diff --git a/.editorconfig b/.editorconfig index b39bbc2f71..238e9887bd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -64,7 +64,7 @@ dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_style.static_prefix_style.required_prefix = s_ -dotnet_naming_style.static_prefix_style.capitalization = camel_case +dotnet_naming_style.static_prefix_style.capitalization = camel_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion @@ -137,12 +137,20 @@ space_within_single_line_array_initializer_braces = true #Net Analyzer dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = suggestion + +# CA1304: Specify CultureInfo +dotnet_diagnostic.CA1304.severity = warning # CA1802: Use literals where appropriate dotnet_diagnostic.CA1802.severity = warning # CA1820: Test for empty strings using string length dotnet_diagnostic.CA1820.severity = warning # CA1821: Remove empty finalizers dotnet_diagnostic.CA1821.severity = warning +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = suggestion +dotnet_code_quality.CA1822.api_surface = private, internal # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning # CA1826: Use property instead of Linq Enumerable method diff --git a/.ncrunch/ReactiveUIDemo.v3.ncrunchproject b/.ncrunch/ReactiveUIDemo.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/ReactiveUIDemo.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 6ba05332be..3fa8e969c8 100644 --- a/Avalonia.Desktop.slnf +++ b/Avalonia.Desktop.slnf @@ -9,6 +9,7 @@ "samples\\MiniMvvm\\MiniMvvm.csproj", "samples\\SampleControls\\ControlSamples.csproj", "samples\\Sandbox\\Sandbox.csproj", + "samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj", "src\\Avalonia.Base\\Avalonia.Base.csproj", "src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj", "src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj", diff --git a/Avalonia.sln b/Avalonia.sln index 34b5596119..7efb294b64 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -40,6 +40,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE .editorconfig = .editorconfig src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs + src\Shared\NullableAttributes.cs = src\Shared\NullableAttributes.cs src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs = src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs EndProjectSection @@ -119,6 +120,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\SourceLink.props = build\SourceLink.props build\System.Drawing.Common.props = build\System.Drawing.Common.props build\System.Memory.props = build\System.Memory.props + build\TrimmingEnable.props = build\TrimmingEnable.props build\UnitTests.NetFX.props = build\UnitTests.NetFX.props build\XUnit.props = build\XUnit.props EndProjectSection @@ -222,14 +224,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "sample EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Browser", "src\Browser\Avalonia.Browser\Avalonia.Browser.csproj", "{4A39637C-9338-4925-A4DB-D072E292EC78}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser", "src\Browser\Avalonia.Browser\Avalonia.Browser.csproj", "{4A39637C-9338-4925-A4DB-D072E292EC78}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -541,6 +545,10 @@ Global {90B08091-9BBD-4362-B712-E9F2CC62B218}.Debug|Any CPU.Build.0 = Debug|Any CPU {90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.ActiveCfg = Release|Any CPU {90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.Build.0 = Release|Any CPU + {75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -605,6 +613,7 @@ Global {47F8530C-F19B-4B1A-B4D6-EB231522AE5D} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098} {90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 5838519596..e9c3d65b41 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -7,7 +7,7 @@ https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ true - CS1591 + $(NoWarn);CS1591 preview MIT Icon.png diff --git a/build/TrimmingEnable.props b/build/TrimmingEnable.props new file mode 100644 index 0000000000..3f873e3345 --- /dev/null +++ b/build/TrimmingEnable.props @@ -0,0 +1,16 @@ + + + false + true + false + true + + + + true + + $(WarningsAsErrors);IL2000;IL2001;IL2002;IL2003;IL2004;IL2005;IL2006;IL2007;IL2008;IL2009;IL2010;IL2011;IL2012;IL2013;IL2014;IL2015;IL2016;IL2017;IL2018;IL2019;IL2020;IL2021;IL2022;IL2023;IL2024;IL2025;IL2026;IL2027;IL2028;IL2029;IL2030;IL2031;IL2032;IL2033;IL2034;IL2035;IL2036;IL2037;IL2038;IL2039;IL2040;IL2041;IL2042;IL2043;IL2044;IL2045;IL2046;IL2047;IL2048;IL2049;IL2050;IL2051;IL2052;IL2053;IL2054;IL2055;IL2056;IL2057;IL2058;IL2059;IL2060;IL2061;IL2062;IL2063;IL2064;IL2065;IL2066;IL2067;IL2068;IL2069;IL2070;IL2071;IL2072;IL2073;IL2074;IL2075;IL2076;IL2077;IL2078;IL2079;IL2080;IL2081;IL2082;IL2083;IL2084;IL2085;IL2086;IL2087;IL2088;IL2089;IL2090;IL2091;IL2092;IL2093;IL2094;IL2095;IL2096;IL2097;IL2098;IL2099;IL2100;IL2101;IL2102;IL2103;IL2104;IL2105;IL2106;IL2107;IL2108;IL2109;IL2110;IL2111;IL2112;IL2113;IL2114;IL2115;IL2116;IL2117;IL2118;IL2119;IL2120;IL2121;IL2122;IL2123;IL2124;IL2125;IL2126;IL2127;IL2128;IL2129;IL2130;IL2131;IL2132;IL2133;IL2134;IL2135;IL2136;IL2137;IL2138;IL2139;IL2140;IL2141;IL2142;IL2143;IL2144;IL2145;IL2146;IL2147;IL2148;IL2149;IL2150;IL2151;IL2152;IL2153;IL2154;IL2155;IL2156;IL2157 + + $(WarningsAsErrors);IL3050;IL3051;IL3052;IL3053;IL3054;IL3055;IL3056 + + diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 2443965957..b6dacb6ce4 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -137,7 +137,11 @@ void WindowImpl::BringToFront() for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) { - (*iterator)->BringToFront(); + auto window = (*iterator)->Window; + + // #9565: Only bring window to front if it's on the currently active space + if ([window isOnActiveSpace]) + (*iterator)->BringToFront(); } } } @@ -161,6 +165,9 @@ void WindowImpl::StartStateTransition() { void WindowImpl::EndStateTransition() { _transitioningWindowState = false; + + // Ensure correct order of child windows after fullscreen transition. + BringToFront(); } SystemDecorations WindowImpl::Decorations() { diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 2295c0beda..3704cee890 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -80,7 +80,6 @@ partial class Build : NukeBuild if (Parameters.IsRunningOnAzure) c.AddProperty("JavaSdkDirectory", GetVariable("JAVA_HOME_11_X64")); c.AddProperty("PackageVersion", Parameters.Version) - .AddProperty("iOSRoslynPathHackRequired", true) .SetConfiguration(Parameters.Configuration) .SetVerbosity(DotNetVerbosity.Minimal); return c; diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 92d9732e91..13bac4b7db 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -4,7 +4,7 @@ false False - CS0649;CS0169;SYSLIB0011 + $(NoWarn);CS0649;CS0169;SYSLIB0011 1 net7.0 diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 1b822c14cf..ca2d4b66ed 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -30,7 +30,9 @@ /> - + $(IntermediateOutputPath)/Avalonia/resources @@ -62,7 +64,9 @@ BeforeTargets="CoreCompile;CoreResGen" Inputs="@(AvaloniaResource);@(AvaloniaXaml);@(CustomAdditionalGenerateAvaloniaResourcesInputs);$(MSBuildAllProjects)" Outputs="$(AvaloniaResourcesTemporaryFilePath)" - DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)"> + DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)" + Condition="('@(AvaloniaResource->Count())' > 0) or ('@(AvaloniaXaml->Count())' > 0)" + > @@ -81,7 +85,12 @@ $(IntermediateOutputPath)/Avalonia/references diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index 8d9cd74ea7..ae7e43f511 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -14,7 +14,26 @@ namespace ControlCatalog.Pages { public class AutoCompleteBoxPage : UserControl { - private StateData[] BuildAllStates() + public class StateData + { + public string Name { get; private set; } + public string Abbreviation { get; private set; } + public string Capital { get; private set; } + + public StateData(string name, string abbreviatoin, string capital) + { + Name = name; + Abbreviation = abbreviatoin; + Capital = capital; + } + + public override string ToString() + { + return Name; + } + } + + private static StateData[] BuildAllStates() { return new StateData[] { @@ -72,7 +91,7 @@ namespace ControlCatalog.Pages } public StateData[] States { get; private set; } - private LinkedList[] BuildAllSentences() + private static LinkedList[] BuildAllSentences() { return new string[] { @@ -124,7 +143,7 @@ namespace ControlCatalog.Pages .OfType(); } - private bool StringContains(string str, string? query) + private static bool StringContains(string str, string? query) { if (query == null) return false; return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index 61e0ed5acb..c70675b606 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -25,7 +25,7 @@ public partial class CompositionPage : UserControl this.Get("Items").Items = CreateColorItems(); } - private List CreateColorItems() + private static List CreateColorItems() { var list = new List(); diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs index 45043aa5af..511b01c7ac 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs +++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs @@ -58,7 +58,7 @@ namespace ControlCatalog.Pages } } - private PixelRect GetCropRect(int index) + private static PixelRect GetCropRect(int index) { var bitmapWidth = 640; var bitmapHeight = 426; diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index a126fbefe5..ded02330d5 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -247,7 +247,7 @@ namespace ControlCatalog.Pages } - private void CheckError(GlInterface gl) + private static void CheckError(GlInterface gl) { int err; while ((err = gl.GetError()) != GL_NO_ERROR) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index ff62b834c4..6af4cf353e 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -66,6 +66,7 @@ namespace ControlCatalog.Pages context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); formattedText = @@ -77,7 +78,7 @@ namespace ControlCatalog.Pages context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10)); } - private FormattedText CreateFormattedText(string textToFormat) + private static FormattedText CreateFormattedText(string textToFormat) { return new FormattedText(textToFormat, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, 12, Brushes.Green); diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs index 74e9928b24..413b6e1c75 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -49,7 +49,7 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } - private IBitmap LoadBitmap(string uri) + private static IBitmap LoadBitmap(string uri) { var assets = AvaloniaLocator.Current!.GetService()!; return new Bitmap(assets.Open(new Uri(uri))); diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 038ced4e5c..54c0cb0655 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -17,11 +17,15 @@ - + + + WindowState: + + @@ -129,13 +133,14 @@ CenterOwner - Normal - Minimized - Maximized - FullScreen + Normal + Minimized + Maximized + FullScreen + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index c1acc7ca88..841947673a 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Avalonia; @@ -178,6 +179,8 @@ namespace IntegrationTestApp ShowWindow(); if (source?.Name == "SendToBack") SendToBack(); + if (source?.Name == "EnterFullscreen") + WindowState = WindowState.FullScreen; if (source?.Name == "ExitFullscreen") WindowState = WindowState.Normal; if (source?.Name == "RestoreAll") diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index c3a0d8d2e2..00987429d0 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -27,10 +27,10 @@ - Normal - Minimized - Maximized - FullScreen + Normal + Minimized + Maximized + FullScreen diff --git a/samples/ReactiveUIDemo/App.axaml b/samples/ReactiveUIDemo/App.axaml new file mode 100644 index 0000000000..dd3a39f6ac --- /dev/null +++ b/samples/ReactiveUIDemo/App.axaml @@ -0,0 +1,8 @@ + + + + + diff --git a/samples/ReactiveUIDemo/App.axaml.cs b/samples/ReactiveUIDemo/App.axaml.cs new file mode 100644 index 0000000000..4578566427 --- /dev/null +++ b/samples/ReactiveUIDemo/App.axaml.cs @@ -0,0 +1,37 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; +using ReactiveUI; +using ReactiveUIDemo.ViewModels; +using ReactiveUIDemo.Views; +using Splat; + +namespace ReactiveUIDemo +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + Locator.CurrentMutable.Register(() => new FooView(), typeof(IViewFor)); + Locator.CurrentMutable.Register(() => new BarView(), typeof(IViewFor)); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + desktop.MainWindow = new MainWindow(); + base.OnFrameworkInitializationCompleted(); + } + + public static int Main(string[] args) + => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .UseReactiveUI() + .LogToTrace(); + } +} diff --git a/samples/ReactiveUIDemo/MainWindow.axaml b/samples/ReactiveUIDemo/MainWindow.axaml new file mode 100644 index 0000000000..7775fc5a79 --- /dev/null +++ b/samples/ReactiveUIDemo/MainWindow.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/samples/ReactiveUIDemo/MainWindow.axaml.cs b/samples/ReactiveUIDemo/MainWindow.axaml.cs new file mode 100644 index 0000000000..5bf2d476fd --- /dev/null +++ b/samples/ReactiveUIDemo/MainWindow.axaml.cs @@ -0,0 +1,22 @@ +using ReactiveUIDemo.ViewModels; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ReactiveUIDemo +{ + public class MainWindow : Window + { + public MainWindow() + { + this.InitializeComponent(); + this.DataContext = new MainWindowViewModel(); + this.AttachDevTools(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ReactiveUIDemo/ReactiveUIDemo.csproj b/samples/ReactiveUIDemo/ReactiveUIDemo.csproj new file mode 100644 index 0000000000..94ca4ee809 --- /dev/null +++ b/samples/ReactiveUIDemo/ReactiveUIDemo.csproj @@ -0,0 +1,28 @@ + + + Exe + net6.0 + enable + + + + + + + + + + + BarView.axaml + + + FooView.axaml + + + + + + + + + diff --git a/samples/ReactiveUIDemo/ViewModels/BarViewModel.cs b/samples/ReactiveUIDemo/ViewModels/BarViewModel.cs new file mode 100644 index 0000000000..3448453d81 --- /dev/null +++ b/samples/ReactiveUIDemo/ViewModels/BarViewModel.cs @@ -0,0 +1,11 @@ +using ReactiveUI; + +namespace ReactiveUIDemo.ViewModels +{ + internal class BarViewModel : ReactiveObject, IRoutableViewModel + { + public BarViewModel(IScreen screen) => HostScreen = screen; + public string UrlPathSegment => "Bar"; + public IScreen HostScreen { get; } + } +} diff --git a/samples/ReactiveUIDemo/ViewModels/FooViewModel.cs b/samples/ReactiveUIDemo/ViewModels/FooViewModel.cs new file mode 100644 index 0000000000..1a363e18dc --- /dev/null +++ b/samples/ReactiveUIDemo/ViewModels/FooViewModel.cs @@ -0,0 +1,11 @@ +using ReactiveUI; + +namespace ReactiveUIDemo.ViewModels +{ + internal class FooViewModel : ReactiveObject, IRoutableViewModel + { + public FooViewModel(IScreen screen) => HostScreen = screen; + public string UrlPathSegment => "Foo"; + public IScreen HostScreen { get; } + } +} diff --git a/samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs b/samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000000..2222137d38 --- /dev/null +++ b/samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,9 @@ +using ReactiveUI; + +namespace ReactiveUIDemo.ViewModels +{ + internal class MainWindowViewModel : ReactiveObject + { + public RoutedViewHostPageViewModel RoutedViewHost { get; } = new(); + } +} diff --git a/samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs b/samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs new file mode 100644 index 0000000000..701447cfe8 --- /dev/null +++ b/samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs @@ -0,0 +1,21 @@ +using ReactiveUI; + +namespace ReactiveUIDemo.ViewModels +{ + internal class RoutedViewHostPageViewModel : ReactiveObject, IScreen + { + public RoutedViewHostPageViewModel() + { + Foo = new(this); + Bar = new(this); + Router.Navigate.Execute(Foo); + } + + public RoutingState Router { get; } = new(); + public FooViewModel Foo { get; } + public BarViewModel Bar { get; } + + public void ShowFoo() => Router.Navigate.Execute(Foo); + public void ShowBar() => Router.Navigate.Execute(Bar); + } +} diff --git a/samples/ReactiveUIDemo/Views/BarView.axaml b/samples/ReactiveUIDemo/Views/BarView.axaml new file mode 100644 index 0000000000..2622245997 --- /dev/null +++ b/samples/ReactiveUIDemo/Views/BarView.axaml @@ -0,0 +1,16 @@ + + + + Bar! + + + diff --git a/samples/ReactiveUIDemo/Views/BarView.axaml.cs b/samples/ReactiveUIDemo/Views/BarView.axaml.cs new file mode 100644 index 0000000000..2fbea6de91 --- /dev/null +++ b/samples/ReactiveUIDemo/Views/BarView.axaml.cs @@ -0,0 +1,28 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using ReactiveUI; +using ReactiveUIDemo.ViewModels; + +namespace ReactiveUIDemo.Views +{ + internal partial class BarView : UserControl, IViewFor + { + public BarView() + { + InitializeComponent(); + } + + public BarViewModel? ViewModel { get; set; } + + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (BarViewModel?)value; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ReactiveUIDemo/Views/FooView.axaml b/samples/ReactiveUIDemo/Views/FooView.axaml new file mode 100644 index 0000000000..8f73250d3b --- /dev/null +++ b/samples/ReactiveUIDemo/Views/FooView.axaml @@ -0,0 +1,16 @@ + + + + Foo! + + + diff --git a/samples/ReactiveUIDemo/Views/FooView.axaml.cs b/samples/ReactiveUIDemo/Views/FooView.axaml.cs new file mode 100644 index 0000000000..313a71044c --- /dev/null +++ b/samples/ReactiveUIDemo/Views/FooView.axaml.cs @@ -0,0 +1,28 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using ReactiveUI; +using ReactiveUIDemo.ViewModels; + +namespace ReactiveUIDemo.Views +{ + internal partial class FooView : UserControl, IViewFor + { + public FooView() + { + InitializeComponent(); + } + + public FooViewModel? ViewModel { get; set; } + + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (FooViewModel?)value; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 6688dde8f5..66557418dd 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -16,4 +16,5 @@ + diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index 57f6469a42..bc24a16e56 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -30,7 +30,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers return DispatchKeyEventInternal(e, out callBase); } - string UnicodeTextInput(KeyEvent keyEvent) + static string UnicodeTextInput(KeyEvent keyEvent) { return keyEvent.Action == KeyEventActions.Multiple && keyEvent.RepeatCount == 0 diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs index a9b2e16d43..078f70db60 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs @@ -178,7 +178,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF return false; } - private Stream? GetVirtualFileStream(Context context, AndroidUri uri, bool isOutput) + private static Stream? GetVirtualFileStream(Context context, AndroidUri uri, bool isOutput) { var mimeTypes = context.ContentResolver?.GetStreamTypes(uri, FilePickerFileTypes.All.MimeTypes![0]); if (mimeTypes?.Length >= 1) diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs index e0883901fd..06087cdd6a 100644 --- a/src/Avalonia.Base/Animation/Animation.cs +++ b/src/Avalonia.Base/Animation/Animation.cs @@ -202,9 +202,7 @@ namespace Avalonia.Animation /// The animation setter. /// The property animator value. public static void SetAnimator(IAnimationSetter setter, -#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)] -#endif Type value) { s_animators[setter] = (value, () => (IAnimator)Activator.CreateInstance(value)!); diff --git a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs index 0356723f00..3168a67d79 100644 --- a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Avalonia.Animation.Animators; using Avalonia.Data; using Avalonia.Reactive; @@ -66,7 +67,8 @@ namespace Avalonia.Animation } } - public T GetTypedValue() + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] + public T GetTypedValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() { var typeConv = TypeDescriptor.GetConverter(typeof(T)); diff --git a/src/Avalonia.Base/Animation/Animators/Animator`1.cs b/src/Avalonia.Base/Animation/Animators/Animator`1.cs index 8765cfb4c9..b5d1feb4a7 100644 --- a/src/Avalonia.Base/Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Base/Animation/Animators/Animator`1.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Animation.Utils; using Avalonia.Collections; @@ -39,7 +40,7 @@ namespace Avalonia.Animation.Animators VerifyConvertKeyFrames(); var subject = new DisposeAnimationInstanceSubject(this, animation, control, clock, onComplete); - return match.Subscribe(subject); + return new CompositeDisposable(match.Subscribe(subject), subject); } protected T InterpolationHandler(double animationTime, T neutralValue) diff --git a/src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs index 4727ea1bfb..068c190fa1 100644 --- a/src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs @@ -72,7 +72,7 @@ namespace Avalonia.Animation.Animators return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } - private IReadOnlyList InterpolateStops(double progress, IReadOnlyList oldValue, IReadOnlyList newValue) + private static IReadOnlyList InterpolateStops(double progress, IReadOnlyList oldValue, IReadOnlyList newValue) { var resultCount = Math.Max(oldValue.Count, newValue.Count); var stops = new ImmutableGradientStop[resultCount]; diff --git a/src/Avalonia.Base/Animation/KeySpline.cs b/src/Avalonia.Base/Animation/KeySpline.cs index a6e9769186..6ca5b2e759 100644 --- a/src/Avalonia.Base/Animation/KeySpline.cs +++ b/src/Avalonia.Base/Animation/KeySpline.cs @@ -196,7 +196,7 @@ namespace Avalonia.Animation /// /// /// - private bool IsValidXValue(double value) + private static bool IsValidXValue(double value) { return value >= 0.0 && value <= 1.0; } diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 21bdb794b3..0d3da66f7a 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -19,6 +19,7 @@ + @@ -30,6 +31,8 @@ + + @@ -41,6 +44,7 @@ + @@ -48,14 +52,10 @@ - + - diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 2c89062e51..e0782c51a2 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.PropertyStore; @@ -442,6 +443,7 @@ namespace Avalonia /// /// The value. /// True if the value is valid, otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public bool IsValidValue(object? value) { return TypeUtilities.TryConvertImplicit(PropertyType, value, out _); diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 6106c58880..fc0ca2323e 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Avalonia @@ -42,6 +43,7 @@ namespace Avalonia /// /// The type. /// A collection of definitions. + [UnconditionalSuppressMessage("Trimming", "IL2059", Justification = "If type was trimmed out, no properties were referenced")] public IReadOnlyList GetRegistered(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 5a0d69f3bf..53444ee475 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Utilities; @@ -67,6 +68,7 @@ namespace Avalonia protected override IObservable GetChanged() => Changed; + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] protected BindingValue TryConvert(object? value) { if (value == UnsetValue) diff --git a/src/Avalonia.Base/Collections/AvaloniaListConverter.cs b/src/Avalonia.Base/Collections/AvaloniaListConverter.cs index b3fc0b01b6..34ccb5e65f 100644 --- a/src/Avalonia.Base/Collections/AvaloniaListConverter.cs +++ b/src/Avalonia.Base/Collections/AvaloniaListConverter.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using Avalonia.Utilities; @@ -8,7 +9,8 @@ namespace Avalonia.Collections /// /// Creates an from a string representation. /// - public class AvaloniaListConverter : TypeConverter + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] + public class AvaloniaListConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { diff --git a/src/Avalonia.Base/Compatibility/TrimmingAttributes.cs b/src/Avalonia.Base/Compatibility/TrimmingAttributes.cs new file mode 100644 index 0000000000..941faa46bc --- /dev/null +++ b/src/Avalonia.Base/Compatibility/TrimmingAttributes.cs @@ -0,0 +1,121 @@ +#pragma warning disable MA0048 // File name must match type name +// https://github.com/dotnet/runtime/tree/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Diagnostics.CodeAnalysis +{ +#nullable enable +#if !NET6_0_OR_GREATER + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } + + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + None = 0, + PublicParameterlessConstructor = 0x0001, + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + NonPublicConstructors = 0x0004, + PublicMethods = 0x0008, + NonPublicMethods = 0x0010, + PublicFields = 0x0020, + NonPublicFields = 0x0040, + PublicNestedTypes = 0x0080, + NonPublicNestedTypes = 0x0100, + PublicProperties = 0x0200, + NonPublicProperties = 0x0400, + PublicEvents = 0x0800, + NonPublicEvents = 0x1000, + Interfaces = 0x2000, + All = ~None + } + + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method, + AllowMultiple = true, Inherited = false)] + internal sealed class DynamicDependencyAttribute : Attribute + { + public DynamicDependencyAttribute(string memberSignature) + { + MemberSignature = memberSignature; + } + + public DynamicDependencyAttribute(string memberSignature, Type type) + { + MemberSignature = memberSignature; + Type = type; + } + + public DynamicDependencyAttribute(string memberSignature, string typeName, string assemblyName) + { + MemberSignature = memberSignature; + TypeName = typeName; + AssemblyName = assemblyName; + } + + public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, Type type) + { + MemberTypes = memberTypes; + Type = type; + } + + public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, string typeName, string assemblyName) + { + MemberTypes = memberTypes; + TypeName = typeName; + AssemblyName = assemblyName; + } + + public string? MemberSignature { get; } + public DynamicallyAccessedMemberTypes MemberTypes { get; } + public Type? Type { get; } + public string? TypeName { get; } + public string? AssemblyName { get; } + public string? Condition { get; set; } + } + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] + internal sealed class RequiresUnreferencedCodeAttribute : Attribute + { + public RequiresUnreferencedCodeAttribute(string message) + { + Message = message; + } + + public string Message { get; } + public string? Url { get; set; } + } + + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] + internal sealed class UnconditionalSuppressMessageAttribute : Attribute + { + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + public string Category { get; } + public string CheckId { get; } + public string? Scope { get; set; } + public string? Target { get; set; } + public string? MessageId { get; set; } + public string? Justification { get; set; } + } +#endif +} + diff --git a/src/Avalonia.Base/Controls/Classes.cs b/src/Avalonia.Base/Controls/Classes.cs index 30c6fdefa3..e41e1cc488 100644 --- a/src/Avalonia.Base/Controls/Classes.cs +++ b/src/Avalonia.Base/Controls/Classes.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls /// The initial items. public Classes(params string[] items) : base(items) - { + { } /// @@ -320,7 +320,7 @@ namespace Avalonia.Controls listener.Changed(); } - private void ThrowIfPseudoclass(string name, string operation) + private static void ThrowIfPseudoclass(string name, string operation) { if (name.StartsWith(":")) { diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 4bb3ad08d5..4e07ebf445 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Avalonia.Utilities; namespace Avalonia.Data @@ -236,6 +237,7 @@ namespace Avalonia.Data /// /// The untyped value. /// The typed binding value. + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static BindingValue FromUntyped(object? value) { return FromUntyped(value, typeof(T)); @@ -249,6 +251,7 @@ namespace Avalonia.Data /// The untyped value. /// The runtime target type. /// The typed binding value. + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static BindingValue FromUntyped(object? value, Type targetType) { if (value == AvaloniaProperty.UnsetValue) diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index c4f4362537..f5c135459d 100644 --- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Windows.Input; using Avalonia.Utilities; @@ -9,6 +10,7 @@ namespace Avalonia.Data.Converters /// Provides a default set of value conversions for bindings that do not specify a value /// converter. /// + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] public class DefaultValueConverter : IValueConverter { /// diff --git a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs b/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs index 0672185a55..b42debc358 100644 --- a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs +++ b/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -9,6 +10,7 @@ using Avalonia.Utilities; namespace Avalonia.Data.Converters { + [RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)] class MethodToCommandConverter : ICommand { readonly static Func AlwaysEnabled = (_) => true; diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index ea903c1042..f60b4722d9 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -13,6 +14,7 @@ namespace Avalonia.Data.Core /// Binds to an expression on an object using a type value converter to convert the values /// that are sent and received. /// + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] public class BindingExpression : LightweightObservableBase, ISubject, IDescription { private readonly ExpressionObserver _inner; diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index 4f755ff140..e4b833176c 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -159,7 +159,7 @@ namespace Avalonia.Data.Core _listening = false; } - private BindingNotification TargetNullNotification() + private static BindingNotification TargetNullNotification() { return new BindingNotification( new MarkupBindingChainException("Null value"), diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 0c7f576da6..0a9f834aeb 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reactive; using System.Reactive.Linq; @@ -126,6 +127,7 @@ namespace Avalonia.Data.Core /// /// A description of the expression. If null, 's string representation will be used. /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( T? root, Expression> expression, @@ -144,6 +146,7 @@ namespace Avalonia.Data.Core /// /// A description of the expression. If null, 's string representation will be used. /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( IObservable rootObservable, Expression> expression, @@ -168,6 +171,7 @@ namespace Avalonia.Data.Core /// /// A description of the expression. If null, 's string representation will be used. /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( Func rootGetter, Expression> expression, @@ -283,6 +287,7 @@ namespace Avalonia.Data.Core } } + [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)] private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation) { return ExpressionTreeParser.Parse(expression, enableDataValidation); diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs index d2035a592f..c48be6a175 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs +++ b/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs @@ -1,10 +1,12 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Linq.Expressions; namespace Avalonia.Data.Core.Parsers { static class ExpressionTreeParser { + [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)] public static ExpressionNode Parse(Expression expr, bool enableDataValidation) { var visitor = new ExpressionVisitorNodeBuilder(enableDataValidation); diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs index 1e82214d76..e1e5a705f0 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs +++ b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Avalonia.Data.Core.Parsers { + [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)] class ExpressionVisitorNodeBuilder : ExpressionVisitor { private const string MultiDimensionalArrayGetterMethodName = "Get"; @@ -81,7 +83,7 @@ namespace Avalonia.Data.Core.Parsers return node; } - private T GetArgumentExpressionValue(Expression expr) + private static T GetArgumentExpressionValue(Expression expr) { try { @@ -193,7 +195,7 @@ namespace Avalonia.Data.Core.Parsers throw new ExpressionParseException(0, $"Invalid method call in binding expression: '{node.Method.DeclaringType!.AssemblyQualifiedName}.{node.Method.Name}'."); } - private PropertyInfo? TryGetPropertyFromMethod(MethodInfo method) + private static PropertyInfo? TryGetPropertyFromMethod(MethodInfo method) { var type = method.DeclaringType; return type?.GetRuntimeProperties().FirstOrDefault(prop => prop.GetMethod == method); diff --git a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs index cc6d92ceb7..34f8e568d4 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using Avalonia.Utilities; @@ -10,6 +11,7 @@ namespace Avalonia.Data.Core.Plugins public class AvaloniaPropertyAccessorPlugin : IPropertyAccessorPlugin { /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public bool Match(object obj, string propertyName) { if (obj is AvaloniaObject o) @@ -29,6 +31,7 @@ namespace Avalonia.Data.Core.Plugins /// An interface through which future interactions with the /// property will be made. /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public IPropertyAccessor? Start(WeakReference reference, string propertyName) { _ = reference ?? throw new ArgumentNullException(nameof(reference)); diff --git a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs index 361d68dc81..bc300386b9 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -12,6 +13,7 @@ namespace Avalonia.Data.Core.Plugins public class DataAnnotationsValidationPlugin : IDataValidationPlugin { /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public bool Match(WeakReference reference, string memberName) { reference.TryGetTarget(out var target); @@ -24,11 +26,13 @@ namespace Avalonia.Data.Core.Plugins } /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner) { return new Accessor(reference, name, inner); } + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] private sealed class Accessor : DataValidationBase { private readonly ValidationContext? _context; @@ -63,7 +67,7 @@ namespace Avalonia.Data.Core.Plugins } } - private Exception CreateException(IList errors) + private static Exception CreateException(IList errors) { if (errors.Count == 1) { diff --git a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs index bf4a0a88bd..2bb8da2c74 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace Avalonia.Data.Core.Plugins @@ -9,9 +10,11 @@ namespace Avalonia.Data.Core.Plugins public class ExceptionValidationPlugin : IDataValidationPlugin { /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public bool Match(WeakReference reference, string memberName) => true; /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner) { return new Validator(reference, name, inner); diff --git a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs index 88e38a8d08..5b4d7cd3a1 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace Avalonia.Data.Core.Plugins { @@ -13,6 +14,7 @@ namespace Avalonia.Data.Core.Plugins /// A weak reference to the object. /// The name of the member to validate. /// True if the plugin can handle the object; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] bool Match(WeakReference reference, string memberName); /// @@ -25,6 +27,7 @@ namespace Avalonia.Data.Core.Plugins /// An interface through which future interactions with the /// property will be made. /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] IPropertyAccessor Start(WeakReference reference, string propertyName, IPropertyAccessor inner); diff --git a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs index f000427de3..04601bc8b2 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace Avalonia.Data.Core.Plugins { @@ -14,6 +15,7 @@ namespace Avalonia.Data.Core.Plugins /// The object. /// The property name. /// True if the plugin can handle the property on the object; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] bool Match(object obj, string propertyName); /// @@ -25,6 +27,7 @@ namespace Avalonia.Data.Core.Plugins /// An interface through which future interactions with the /// property will be made. /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] IPropertyAccessor? Start(WeakReference reference, string propertyName); } diff --git a/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs index b741cfaca2..8bf2d6c3d6 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace Avalonia.Data.Core.Plugins { @@ -12,6 +13,7 @@ namespace Avalonia.Data.Core.Plugins /// /// A weak reference to the value. /// True if the plugin can handle the value; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] bool Match(WeakReference reference); /// @@ -21,6 +23,7 @@ namespace Avalonia.Data.Core.Plugins /// /// An observable that produces the output for the value. /// + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] IObservable Start(WeakReference reference); } } diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 1e7a0d5c8f..87a2f67ee8 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Avalonia.Utilities; @@ -18,6 +19,7 @@ namespace Avalonia.Data.Core.Plugins ); /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public bool Match(WeakReference reference, string memberName) { reference.TryGetTarget(out var target); @@ -26,6 +28,7 @@ namespace Avalonia.Data.Core.Plugins } /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor accessor) { return new Validator(reference, name, accessor); @@ -108,7 +111,7 @@ namespace Avalonia.Data.Core.Plugins return target; } - private Exception GenerateException(IList errors) + private static Exception GenerateException(IList errors) { if (errors.Count == 1) { diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 91d69b5d3d..5b19e995cc 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Avalonia.Utilities; @@ -17,6 +17,7 @@ namespace Avalonia.Data.Core.Plugins new Dictionary<(Type, string), PropertyInfo?>(); /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null; /// @@ -28,6 +29,7 @@ namespace Avalonia.Data.Core.Plugins /// An interface through which future interactions with the /// property will be made. /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public IPropertyAccessor? Start(WeakReference reference, string propertyName) { _ = reference ?? throw new ArgumentNullException(nameof(reference)); @@ -52,7 +54,8 @@ namespace Avalonia.Data.Core.Plugins private const BindingFlags PropertyBindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; - + + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName) { if (instance is IReflectableType reflectableType && instance is not Type) @@ -70,7 +73,8 @@ namespace Avalonia.Data.Core.Plugins return propertyInfo; } - private PropertyInfo? TryFindAndCacheProperty(Type type, string propertyName) + private PropertyInfo? TryFindAndCacheProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, string propertyName) { PropertyInfo? found = null; diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 0d51a6ed36..2397ce483d 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; @@ -10,8 +11,10 @@ namespace Avalonia.Data.Core.Plugins private readonly Dictionary<(Type, string), MethodInfo?> _methodLookup = new Dictionary<(Type, string), MethodInfo?>(); + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public bool Match(object obj, string methodName) => GetFirstMethodWithName(obj.GetType(), methodName) != null; + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public IPropertyAccessor? Start(WeakReference reference, string methodName) { _ = reference ?? throw new ArgumentNullException(nameof(reference)); @@ -34,7 +37,8 @@ namespace Avalonia.Data.Core.Plugins } } - private MethodInfo? GetFirstMethodWithName(Type type, string methodName) + private MethodInfo? GetFirstMethodWithName( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type, string methodName) { var key = (type, methodName); @@ -46,7 +50,8 @@ namespace Avalonia.Data.Core.Plugins return methodInfo; } - private MethodInfo? TryFindAndCacheMethod(Type type, string methodName) + private MethodInfo? TryFindAndCacheMethod( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type, string methodName) { MethodInfo? found = null; diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index 6232fa39a1..ebee4586db 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reactive.Linq; using System.Reflection; @@ -8,6 +9,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Handles binding to s for the '^' stream binding operator. /// + [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] public class ObservableStreamPlugin : IStreamPlugin { static MethodInfo? observableSelect; @@ -17,6 +19,7 @@ namespace Avalonia.Data.Core.Plugins /// /// A weak reference to the value. /// True if the plugin can handle the value; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] public virtual bool Match(WeakReference reference) { reference.TryGetTarget(out var target); @@ -33,6 +36,7 @@ namespace Avalonia.Data.Core.Plugins /// /// An observable that produces the output for the value. /// + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] public virtual IObservable Start(WeakReference reference) { if (!reference.TryGetTarget(out var target) || target is null) @@ -65,6 +69,7 @@ namespace Avalonia.Data.Core.Plugins new object[] { target, box })!; } + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] private static MethodInfo GetObservableSelect(Type source) { return GetObservableSelect().MakeGenericMethod(source, typeof(object)); diff --git a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs index 377ea9f275..5203aa9f57 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reflection; @@ -9,6 +10,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Handles binding to s for the '^' stream binding operator. /// + [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] public class TaskStreamPlugin : IStreamPlugin { /// @@ -16,12 +18,13 @@ namespace Avalonia.Data.Core.Plugins /// /// A weak reference to the value. /// True if the plugin can handle the value; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] public virtual bool Match(WeakReference reference) { reference.TryGetTarget(out var target); return target is Task; - } + } /// /// Starts producing output based on the specified value. @@ -30,6 +33,7 @@ namespace Avalonia.Data.Core.Plugins /// /// An observable that produces the output for the value. /// + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] public virtual IObservable Start(WeakReference reference) { reference.TryGetTarget(out var target); @@ -59,7 +63,8 @@ namespace Avalonia.Data.Core.Plugins return Observable.Empty(); } - private IObservable HandleCompleted(Task task) + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] + private static IObservable HandleCompleted(Task task) { var resultProperty = task.GetType().GetRuntimeProperty("Result"); diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index c13c6fcbc8..1b79fed6e7 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -1,9 +1,10 @@ using System; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core { + [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)] public class PropertyAccessorNode : SettableNode { private readonly bool _enableValidation; diff --git a/src/Avalonia.Base/Data/Core/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs index 133e6a75f1..6dc6d07184 100644 --- a/src/Avalonia.Base/Data/Core/StreamNode.cs +++ b/src/Avalonia.Base/Data/Core/StreamNode.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reactive.Linq; using Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core { + [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)] public class StreamNode : ExpressionNode { private IStreamPlugin? _customPlugin = null; diff --git a/src/Avalonia.Base/Diagnostics/TrimmingMessages.cs b/src/Avalonia.Base/Diagnostics/TrimmingMessages.cs new file mode 100644 index 0000000000..a893256d17 --- /dev/null +++ b/src/Avalonia.Base/Diagnostics/TrimmingMessages.cs @@ -0,0 +1,30 @@ +namespace Avalonia; + +internal static class TrimmingMessages +{ + public const string ImplicitTypeConvertionSupressWarningMessage = "Implicit convertion methods might be removed by the linker. We don't have a reliable way to prevent it, except converting everything in compile time when possible."; + public const string ImplicitTypeConvertionRequiresUnreferencedCodeMessage = "Implicit convertion methods are required for type conversion."; + + public const string TypeConvertionSupressWarningMessage = "Convertion methods might be removed by the linker. We don't have a reliable way to prevent it, except converting everything in compile time when possible."; + public const string TypeConvertionRequiresUnreferencedCodeMessage = "Convertion methods are required for type conversion, including op_Implicit, op_Explicit, Parse and TypeConverter."; + + public const string ReflectionBindingRequiresUnreferencedCodeMessage = "BindingExpression and ReflectionBinding heavily use reflection. Consider using CompiledBindings instead."; + public const string ReflectionBindingSupressWarningMessage = "BindingExpression and ReflectionBinding internal heavily use reflection."; + + public const string CompiledBindingSafeSupressWarningMessage = "CompiledBinding preserves members used in the expression tree."; + + public const string ExpressionNodeRequiresUnreferencedCodeMessage = "ExpressionNode might require unreferenced code."; + public const string ExpressionSafeSupressWarningMessage = "Typed Expressions preserves members used in the expression tree."; + + public const string SelectorsParseRequiresUnreferencedCodeMessage = "Selectors runtime parser might require unreferenced code. Consider using stronly typed selectors factory with 'new Style(s => s.OfType /// The to round component values for. /// A new with rounded component values. - private HsvColor RoundComponentValues(HsvColor hsvColor) + private static HsvColor RoundComponentValues(HsvColor hsvColor) { return new HsvColor( Math.Round(hsvColor.A, 2, MidpointRounding.AwayFromZero), diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index f0ed89fb3a..2df46889a9 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -1146,7 +1146,7 @@ namespace Avalonia.Controls.Primitives }); } - private void FillPixelForBox( + private static void FillPixelForBox( double x, double y, Hsv baseHsv, diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index ba285a701c..3ec78d6d6a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -56,7 +56,7 @@ namespace Avalonia.Controls set => SetAndRaise(CellEditingTemplateProperty, ref _cellEditingCellTemplate, value); } - private void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) + private static void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) { var oldValue = (IDataTemplate)e.OldValue; var value = (IDataTemplate)e.NewValue; diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index a3c0ed1d0c..fb4335f4de 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -6,8 +6,8 @@ M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z - M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z - M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z + M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z + M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 1dfad7dcc5..604f2369e7 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; using Avalonia.Controls.ApplicationLifetimes; @@ -162,13 +163,6 @@ namespace Avalonia.Controls return Self; } - /// - /// Specifies a windowing subsystem to use. - /// - /// The dll in which to look for subsystem. - /// An instance. - public TAppBuilder UseWindowingSubsystem(string dll) => UseWindowingSubsystem(GetInitializer(dll), dll.Replace("Avalonia.", string.Empty)); - /// /// Specifies a rendering subsystem to use. /// @@ -182,50 +176,8 @@ namespace Avalonia.Controls return Self; } - /// - /// Specifies a rendering subsystem to use. - /// - /// The dll in which to look for subsystem. - /// An instance. - public TAppBuilder UseRenderingSubsystem(string dll) => UseRenderingSubsystem(GetInitializer(dll)); - - static Action GetInitializer(string assemblyName) => () => - { - var assembly = Assembly.Load(new AssemblyName(assemblyName)); - var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform"; - var platformClassFullName = assemblyName + "." + platformClassName; - var platformClass = assembly.GetType(platformClassFullName); - var init = platformClass!.GetRuntimeMethod("Initialize", Type.EmptyTypes); - init!.Invoke(null, null); - }; - - public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); - protected virtual bool CheckSetup => true; - /// - /// Searches and initiates modules included with attribute. - /// - private void SetupAvaloniaModules() - { - var moduleInitializers = from assembly in AppDomain.CurrentDomain.GetAssemblies() - from attribute in assembly.GetCustomAttributes() - where string.IsNullOrEmpty(attribute.ForWindowingSubsystem) - || attribute.ForWindowingSubsystem == WindowingSubsystemName - where string.IsNullOrEmpty(attribute.ForRenderingSubsystem) - || attribute.ForRenderingSubsystem == RenderingSubsystemName - group attribute by attribute.Name into exports - select (from export in exports - orderby export.ForWindowingSubsystem.Length descending - orderby export.ForRenderingSubsystem.Length descending - select export).First().ModuleType into moduleType - select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors - where constructor.GetParameters().Length == 0 && !constructor.IsStatic - select constructor).Single() into constructor - select (Action)(() => constructor.Invoke(Array.Empty())); - Delegate.Combine(moduleInitializers.ToArray())!.DynamicInvoke(); - } - /// /// Configures platform-specific options /// diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs index 5ff291d972..5d71e7a8e4 100644 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs @@ -71,7 +71,7 @@ namespace Avalonia.Automation.Peers } } - private ExpandCollapseState ToState(bool value) + private static ExpandCollapseState ToState(bool value) { return value ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed; } diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index c4587a2d9c..2710ac5cc2 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs index a92feec509..fe8b616e02 100644 --- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs @@ -206,7 +206,7 @@ namespace Avalonia.Controls.Primitives return true; } - private void EnsureValidThread() + private static void EnsureValidThread() { Dispatcher.UIThread.VerifyAccess(); } diff --git a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs index 211b5edb0d..ac4159d536 100644 --- a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs @@ -6,7 +6,6 @@ using Avalonia.Threading; using System; using System.Collections.ObjectModel; -using System.Threading; namespace Avalonia.Controls.Primitives { @@ -353,7 +352,7 @@ namespace Avalonia.Controls.Primitives return true; } - private void EnsureValidThread() + private static void EnsureValidThread() { Dispatcher.UIThread.VerifyAccess(); } diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 2b407cc42a..f02df2e9c1 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -454,10 +454,9 @@ namespace Avalonia.Controls { if (SelectionBoxItem is Rectangle rectangle) { - if ((rectangle.Fill as VisualBrush)?.Visual is Control content) + if ((rectangle.Fill as VisualBrush)?.Visual is Visual content) { - var flowDirection = (((Visual)content!).VisualParent as Control)?.FlowDirection ?? - FlowDirection.LeftToRight; + var flowDirection = content.VisualParent?.FlowDirection ?? FlowDirection.LeftToRight; rectangle.FlowDirection = flowDirection; } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 063e6ae7c8..88c9823952 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -91,13 +91,6 @@ namespace Avalonia.Controls RoutedEvent.Register( nameof(SizeChanged), RoutingStrategies.Direct); - /// - /// Defines the property. - /// - public static readonly AttachedProperty FlowDirectionProperty = - AvaloniaProperty.RegisterAttached( - nameof(FlowDirection), - inherits: true); // Note the following: // _loadedQueue : @@ -170,15 +163,6 @@ namespace Avalonia.Controls get => GetValue(TagProperty); set => SetValue(TagProperty, value); } - - /// - /// Gets or sets the text flow direction. - /// - public FlowDirection FlowDirection - { - get => GetValue(FlowDirectionProperty); - set => SetValue(FlowDirectionProperty, value); - } /// /// Occurs when the user has completed a context input gesture, such as a right-click. @@ -229,39 +213,9 @@ namespace Avalonia.Controls public new Control? Parent => (Control?)base.Parent; - /// - /// Gets the value of the attached on a control. - /// - /// The control. - /// The flow direction. - public static FlowDirection GetFlowDirection(Control control) - { - return control.GetValue(FlowDirectionProperty); - } - - /// - /// Sets the value of the attached on a control. - /// - /// The control. - /// The property value to set. - public static void SetFlowDirection(Control control, FlowDirection value) - { - control.SetValue(FlowDirectionProperty, value); - } - /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; - /// - /// Gets a value indicating whether control bypass FlowDirecton policies. - /// - /// - /// Related to FlowDirection system and returns false as default, so if - /// is RTL then control will get a mirror presentation. - /// For controls that want to avoid this behavior, override this property and return true. - /// - protected virtual bool BypassFlowDirectionPolicies => false; - /// void ISetterValue.Initialize(ISetter setter) { @@ -571,45 +525,6 @@ namespace Avalonia.Controls RaiseEvent(sizeChangedEventArgs); } } - else if (change.Property == FlowDirectionProperty) - { - InvalidateMirrorTransform(); - - foreach (var visual in VisualChildren) - { - if (visual is Control child) - { - child.InvalidateMirrorTransform(); - } - } - } - } - - /// - /// Computes the value according to the - /// and - /// - public virtual void InvalidateMirrorTransform() - { - var flowDirection = this.FlowDirection; - var parentFlowDirection = FlowDirection.LeftToRight; - - bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies; - bool parentBypassFlowDirectionPolicies = false; - - var parent = this.VisualParent as Control; - if (parent != null) - { - parentFlowDirection = parent.FlowDirection; - parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies; - } - - bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies; - bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies; - - bool shouldApplyMirrorTransform = thisShouldBeMirrored != parentShouldBeMirrored; - - HasMirrorTransform = shouldApplyMirrorTransform; } } } diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 8d246d35f4..7737fdac2e 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1165,7 +1165,7 @@ namespace Avalonia.Controls /// /// For "Auto" definitions MinWidth is used in place of PreferredSize. /// - private double GetMeasureSizeForRange( + private static double GetMeasureSizeForRange( IReadOnlyList definitions, int start, int count) @@ -1192,7 +1192,7 @@ namespace Avalonia.Controls /// Starting index of the range. /// Number of definitions included in the range. /// Length type for given range. - private LayoutTimeSizeType GetLengthTypeForRange( + private static LayoutTimeSizeType GetLengthTypeForRange( IReadOnlyList definitions, int start, int count) @@ -1721,7 +1721,7 @@ namespace Avalonia.Controls /// /// Array of definitions to use for calculations. /// Desired size. - private double CalculateDesiredSize( + private static double CalculateDesiredSize( IReadOnlyList definitions) { double desiredSize = 0; @@ -2281,7 +2281,7 @@ namespace Avalonia.Controls /// Start of the range. /// Number of items in the range. /// Final size. - private double GetFinalSizeForRange( + private static double GetFinalSizeForRange( IReadOnlyList definitions, int start, int count) diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs index 9bdefed6b6..4684304725 100644 --- a/src/Avalonia.Controls/GridSplitter.cs +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -516,7 +516,7 @@ namespace Avalonia.Controls /// /// Retrieves the ActualWidth or ActualHeight of the definition depending on its type Column or Row. /// - private double GetActualLength(DefinitionBase definition) + private static double GetActualLength(DefinitionBase definition) { var column = definition as ColumnDefinition; diff --git a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs index 4cc6a20082..381f1799d4 100644 --- a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs +++ b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// /// The registered access keys. /// - private readonly List> _registered = new List>(); + private readonly List<(string AccessKey, IInputElement Element)> _registered = new(); /// /// The window to which the handler belongs. @@ -59,12 +59,12 @@ namespace Avalonia.Controls { var existing = _registered.FirstOrDefault(x => x.Item2 == element); - if (existing != null) + if (existing != default) { _registered.Remove(existing); } - _registered.Add(Tuple.Create(accessKey.ToString().ToUpper(), element)); + _registered.Add((accessKey.ToString().ToUpperInvariant(), element)); } /// @@ -88,9 +88,10 @@ namespace Avalonia.Controls { if (!string.IsNullOrWhiteSpace(e.Text)) { - var text = e.Text.ToUpper(); + var text = e.Text; var focus = _registered - .FirstOrDefault(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)?.Item2; + .FirstOrDefault(x => string.Equals(x.AccessKey, text, StringComparison.OrdinalIgnoreCase) + && x.Element.IsEffectivelyVisible).Element; focus?.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs index ae473f8500..8b3e875e5a 100644 --- a/src/Avalonia.Controls/NativeMenuBar.cs +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; @@ -21,7 +22,16 @@ namespace Avalonia.Controls item.Click -= OnMenuItemClick; }); } - + + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenu))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItem))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemBase))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemSeparator))] + public NativeMenuBar() + { + + } + public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable) { menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable); diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index 45beaa0b2f..46c772f3b1 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -146,7 +146,11 @@ namespace Avalonia.Controls.Notifications { var adornerLayer = host.FindDescendantOfType()?.AdornerLayer; - adornerLayer?.Children.Add(this); + if (adornerLayer is not null) + { + adornerLayer.Children.Add(this); + AdornerLayer.SetAdornedElement(this, adornerLayer); + } } private void UpdatePseudoClasses(NotificationPosition position) diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 676a1587a8..5b2356a7ce 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -69,7 +69,7 @@ namespace Avalonia.Platform return effect; } - private DragDropEffects GetPreferredEffect(DragDropEffects effect, RawInputModifiers modifiers) + private static DragDropEffects GetPreferredEffect(DragDropEffects effect, RawInputModifiers modifiers) { if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None) return effect; // No need to check for the modifiers. @@ -80,7 +80,7 @@ namespace Avalonia.Platform return DragDropEffects.Move; } - private StandardCursorType GetCursorForDropEffect(DragDropEffects effects) + private static StandardCursorType GetCursorForDropEffect(DragDropEffects effects) { if (effects.HasAllFlags(DragDropEffects.Copy)) return StandardCursorType.DragCopy; diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 337ff54985..3c329a9a3e 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; -using System.Linq; using System.Reactive.Disposables; using Avalonia.Automation.Peers; using Avalonia.Controls.Mixins; @@ -15,7 +14,6 @@ using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.VisualTree; using Avalonia.Media; -using Avalonia.Utilities; namespace Avalonia.Controls.Primitives { @@ -639,7 +637,7 @@ namespace Avalonia.Controls.Primitives return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler)); } - private void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint) + private static void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint) { if(host is PopupRoot pr && pr.PlatformImpl is not null) { @@ -769,7 +767,7 @@ namespace Avalonia.Controls.Primitives } } - private void PassThroughEvent(PointerPressedEventArgs e) + private static void PassThroughEvent(PointerPressedEventArgs e) { if (e.Source is LightDismissOverlayLayer layer && layer.GetVisualRoot() is InputElement root) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index f8951b3fa3..44fa78ac21 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -588,7 +588,7 @@ namespace Avalonia.Controls.Primitives } else if (e.Key == Key.Space || e.Key == Key.Enter) { - e.Handled = UpdateSelectionFromEventSource( + UpdateSelectionFromEventSource( e.Source, true, e.KeyModifiers.HasFlag(KeyModifiers.Shift), diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 71a7a58da4..165bec3a95 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -270,15 +270,16 @@ namespace Avalonia.Controls double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum); // When the Orientation changed, the indicator's Width or Height should set to double.NaN. + // Indicator size calculation should consider the ProgressBar's Padding property setting if (Orientation == Orientation.Horizontal) { - _indicator.Width = barSize.Width * percent; + _indicator.Width = (barSize.Width - _indicator.Margin.Left - _indicator.Margin.Right) * percent; _indicator.Height = double.NaN; } else { _indicator.Width = double.NaN; - _indicator.Height = barSize.Height * percent; + _indicator.Height = (barSize.Height - _indicator.Margin.Top - _indicator.Margin.Bottom) * percent; } diff --git a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs index 35bd5aeecb..9132bef3d9 100644 --- a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs +++ b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs @@ -87,7 +87,7 @@ namespace Avalonia.Controls protected virtual string OnSelectTemplateKeyCore(object? dataContext, Control? owner) { - if (SelectTemplateKey is object) + if (SelectTemplateKey is not null) { _args ??= new SelectTemplateEventArgs(); _args.TemplateKey = null; diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs index 29add8720b..fcadafaab5 100644 --- a/src/Avalonia.Controls/SplitView.cs +++ b/src/Avalonia.Controls/SplitView.cs @@ -1,14 +1,11 @@ using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Input; -using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Metadata; -using Avalonia.Platform; using Avalonia.VisualTree; using System; -using System.Reactive.Disposables; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.LogicalTree; @@ -431,7 +428,7 @@ namespace Avalonia.Controls } } - private string GetPseudoClass(SplitViewDisplayMode mode) + private static string GetPseudoClass(SplitViewDisplayMode mode) { return mode switch { @@ -443,7 +440,7 @@ namespace Avalonia.Controls }; } - private string GetPseudoClass(SplitViewPanePlacement placement) + private static string GetPseudoClass(SplitViewPanePlacement placement) { return placement switch { diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 358bcfdad7..699170df5a 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1588,7 +1588,7 @@ namespace Avalonia.Controls private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text); - private int CoerceCaretIndex(int value, string? text) + private static int CoerceCaretIndex(int value, string? text) { if (text == null) { diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 0e04d92d6e..d2d5a8309c 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -357,12 +357,6 @@ namespace Avalonia.Controls /// protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this); - public override void InvalidateMirrorTransform() - { - } - - protected override bool BypassFlowDirectionPolicies => true; - /// /// Handles a paint notification from . /// @@ -427,7 +421,7 @@ namespace Avalonia.Controls LayoutHelper.InvalidateSelfAndChildrenMeasure(this); } - private bool TransparencyLevelsMatch (WindowTransparencyLevel requested, WindowTransparencyLevel received) + private static bool TransparencyLevelsMatch (WindowTransparencyLevel requested, WindowTransparencyLevel received) { if(requested == received) { diff --git a/src/Avalonia.Controls/UserControl.cs b/src/Avalonia.Controls/UserControl.cs index 7b9cc2da1c..e9339d5f4b 100644 --- a/src/Avalonia.Controls/UserControl.cs +++ b/src/Avalonia.Controls/UserControl.cs @@ -1,4 +1,3 @@ -using System; using Avalonia.Styling; namespace Avalonia.Controls diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 0be695e0a1..867c94b126 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -7,7 +7,7 @@ need change when there are breaking changes to designer support api. --> 0.7.0 - CS1591 + $(NoWarn);CS1591 true Avalonia diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index ca2c2c7fb2..543d07f958 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Reflection; using System.Threading; @@ -150,10 +151,20 @@ namespace Avalonia.DesignerSupport.Remote } if (args.Method == Methods.Win32) - builder.UseWindowingSubsystem("Avalonia.Win32"); + builder.UseWindowingSubsystem(GetInitializer("Avalonia.Win32"), "Win32"); builder.SetupWithoutStarting(); return transport; } + + private static Action GetInitializer(string assemblyName) => () => + { + var assembly = Assembly.Load(new AssemblyName(assemblyName)); + var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform"; + var platformClassFullName = assemblyName + "." + platformClassName; + var platformClass = assembly.GetType(platformClassFullName); + var init = platformClass!.GetRuntimeMethod("Initialize", Type.EmptyTypes); + init!.Invoke(null, null); + }; } private const string BuilderMethodName = "BuildAvaloniaApp"; diff --git a/src/Avalonia.Desktop/Avalonia.Desktop.csproj b/src/Avalonia.Desktop/Avalonia.Desktop.csproj index d423aa7dae..d180e6e34d 100644 --- a/src/Avalonia.Desktop/Avalonia.Desktop.csproj +++ b/src/Avalonia.Desktop/Avalonia.Desktop.csproj @@ -12,5 +12,7 @@ - + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs index 444e65e623..2117996b96 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs @@ -47,7 +47,7 @@ namespace Avalonia.Diagnostics.Screenshots /// public string Title { get; } = "Save Screenshot to ..."; - Window GetWindow(Control control) + static Window GetWindow(Control control) { var window = control.VisualRoot as Window; var app = Application.Current; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 0206ede277..5cf9e17ecf 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -82,7 +82,7 @@ namespace Avalonia.Diagnostics.ViewModels { var setterValue = regularSetter.Value; - var resourceInfo = GetResourceInfo(setterValue); + var resourceInfo = GetResourceInfo(setterValue); SetterViewModel setterVm; @@ -121,7 +121,7 @@ namespace Avalonia.Diagnostics.ViewModels public bool CanNavigateToParentProperty => _selectedEntitiesStack.Count >= 1; - private (object resourceKey, bool isDynamic)? GetResourceInfo(object? value) + private static (object resourceKey, bool isDynamic)? GetResourceInfo(object? value) { if (value is StaticResourceExtension staticResource) { @@ -136,7 +136,7 @@ namespace Avalonia.Diagnostics.ViewModels return null; } - private bool IsBinding(object? value) + private static bool IsBinding(object? value) { switch (value) { @@ -253,7 +253,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private IEnumerable GetAvaloniaProperties(object o) + private static IEnumerable GetAvaloniaProperties(object o) { if (o is AvaloniaObject ao) { @@ -267,7 +267,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private IEnumerable GetClrProperties(object o, bool showImplementedInterfaces) + private static IEnumerable GetClrProperties(object o, bool showImplementedInterfaces) { foreach (var p in GetClrProperties(o, o.GetType())) { @@ -286,7 +286,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private IEnumerable GetClrProperties(object o, Type t) + private static IEnumerable GetClrProperties(object o, Type t) { return t.GetProperties() .Where(x => x.GetIndexParameters().Length == 0) @@ -411,7 +411,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private int GroupIndex(string? group) + private static int GroupIndex(string? group) { switch (group) { diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index a311efdfb0..99cf1538ae 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -16,4 +16,5 @@ + diff --git a/src/Avalonia.Dialogs/ManagedStorageProvider.cs b/src/Avalonia.Dialogs/ManagedStorageProvider.cs index 2c88251cca..e45d5c1b4a 100644 --- a/src/Avalonia.Dialogs/ManagedStorageProvider.cs +++ b/src/Avalonia.Dialogs/ManagedStorageProvider.cs @@ -29,7 +29,7 @@ public class ManagedStorageProvider : BclStorageProvider where T : Window, ne public override async Task> OpenFilePickerAsync(FilePickerOpenOptions options) { var model = new ManagedFileChooserViewModel(options, _managedOptions); - var results = await Show(model, _parent); + var results = await ManagedStorageProvider.Show(model, _parent); return results.Select(f => new BclStorageFile(new FileInfo(f))).ToArray(); } @@ -37,7 +37,7 @@ public class ManagedStorageProvider : BclStorageProvider where T : Window, ne public override async Task SaveFilePickerAsync(FilePickerSaveOptions options) { var model = new ManagedFileChooserViewModel(options, _managedOptions); - var results = await Show(model, _parent); + var results = await ManagedStorageProvider.Show(model, _parent); return results.FirstOrDefault() is { } result ? new BclStorageFile(new FileInfo(result)) @@ -47,12 +47,12 @@ public class ManagedStorageProvider : BclStorageProvider where T : Window, ne public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) { var model = new ManagedFileChooserViewModel(options, _managedOptions); - var results = await Show(model, _parent); + var results = await ManagedStorageProvider.Show(model, _parent); return results.Select(f => new BclStorageFolder(new DirectoryInfo(f))).ToArray(); } - private async Task Show(ManagedFileChooserViewModel model, Window parent) + private static async Task Show(ManagedFileChooserViewModel model, Window parent) { var dialog = new T { diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs index 39ddd9d769..34c1506a67 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs @@ -34,14 +34,14 @@ namespace Avalonia.FreeDesktop Poll(0); } - private string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); + private static string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); - private string UnescapeString(string input, string regexText, int escapeBase) => + private static string UnescapeString(string input, string regexText, int escapeBase) => new Regex(regexText).Replace(input, m => Convert.ToChar(Convert.ToByte(m.Groups[1].Value, escapeBase)).ToString()); - private string UnescapePathFromProcMounts(string input) => UnescapeString(input, @"\\(\d{3})", 8); + private static string UnescapePathFromProcMounts(string input) => UnescapeString(input, @"\\(\d{3})", 8); - private string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16); + private static string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16); private void Poll(long _) { diff --git a/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj b/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj index 273910eb59..c713440dc9 100644 --- a/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj +++ b/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj @@ -9,4 +9,5 @@ + diff --git a/src/Avalonia.Headless/Avalonia.Headless.csproj b/src/Avalonia.Headless/Avalonia.Headless.csproj index 2e33b5790a..95f7b79009 100644 --- a/src/Avalonia.Headless/Avalonia.Headless.csproj +++ b/src/Avalonia.Headless/Avalonia.Headless.csproj @@ -5,5 +5,9 @@ - + + + + + diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj index 14dafb7284..b801f8401a 100644 --- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj +++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj @@ -13,4 +13,6 @@ + + diff --git a/src/Avalonia.MicroCom/CallbackBase.cs b/src/Avalonia.MicroCom/CallbackBase.cs index 6c5ab225f6..5279569e83 100644 --- a/src/Avalonia.MicroCom/CallbackBase.cs +++ b/src/Avalonia.MicroCom/CallbackBase.cs @@ -38,7 +38,7 @@ namespace Avalonia.MicroCom } } - public MicroComShadow Shadow { get; set; } + public MicroComShadow? Shadow { get; set; } public void OnReferencedFromNative() { lock (_lock) diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index c924ef3a45..93fba4a57c 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -29,4 +29,5 @@ + diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index d8753efe25..7c7b32f7e4 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -65,7 +65,7 @@ namespace Avalonia.Native } } - private NativeMenu CreateDefaultAppMenu() + private static NativeMenu CreateDefaultAppMenu() { var result = new NativeMenu(); diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs index 7ce50c5126..3e46e0c5c6 100644 --- a/src/Avalonia.Native/IAvnMenu.cs +++ b/src/Avalonia.Native/IAvnMenu.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Reactive.Disposables; using Avalonia.Controls; -using Avalonia.Platform.Interop; namespace Avalonia.Native.Interop { @@ -112,7 +111,7 @@ namespace Avalonia.Native.Interop.Impl return result; } - private __MicroComIAvnMenuItemProxy CreateNew(IAvaloniaNativeFactory factory, NativeMenuItemBase item) + private static __MicroComIAvnMenuItemProxy CreateNew(IAvaloniaNativeFactory factory, NativeMenuItemBase item) { var nativeItem = (__MicroComIAvnMenuItemProxy)(item is NativeMenuItemSeparator ? factory.CreateMenuItemSeparator() : diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index bacb10c792..a9136c2c99 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -12,4 +12,5 @@ + diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index 279e7e750d..e13ee80864 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -38,7 +38,7 @@ namespace Avalonia.OpenGL.Controls base.Render(context); } - private void CheckError(GlInterface gl) + private static void CheckError(GlInterface gl) { int err; while ((err = gl.GetError()) != GL_NO_ERROR) @@ -197,7 +197,7 @@ namespace Avalonia.OpenGL.Controls } } - private bool CheckFramebufferStatus(GlInterface gl) + private static bool CheckFramebufferStatus(GlInterface gl) { var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 348f5f3a10..22ee548823 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -11,4 +11,5 @@ + diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index d746c6db7e..baeb8820f8 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -8,6 +8,8 @@ + + \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs index c58aafa4e0..c18a76711e 100644 --- a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -6,6 +7,7 @@ using Metsys.Bson; namespace Avalonia.Remote.Protocol { + [RequiresUnreferencedCode("Bson uses reflection")] class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection { private readonly IMessageTypeResolver _resolver; diff --git a/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs b/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs index e647f75997..040f90e4bd 100644 --- a/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; namespace Avalonia.Remote.Protocol { + [RequiresUnreferencedCode("Bson uses reflection")] public class BsonTcpTransport : TcpTransportBase { public BsonTcpTransport(IMessageTypeResolver resolver) : base(resolver) @@ -24,4 +26,4 @@ namespace Avalonia.Remote.Protocol return wrap; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs index 8e17dbccfd..3e8b90c741 100644 --- a/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs +++ b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -9,6 +10,9 @@ namespace Avalonia.Remote.Protocol { private readonly Dictionary _guidsToTypes = new Dictionary(); private readonly Dictionary _typesToGuids = new Dictionary(); + + [UnconditionalSuppressMessage("Trimming", "IL2026", + Justification = "If type was trimmed, we don't need to resolve it in the remove protocol")] public DefaultMessageTypeResolver(params Assembly[] assemblies) { foreach (var asm in diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 6abece6bf3..d011a963be 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -32,6 +32,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Linq.Expressions; @@ -71,7 +72,7 @@ namespace Metsys.Bson namespace Metsys.Bson { - + [RequiresUnreferencedCode("Bson uses reflection")] internal class Serializer { private static readonly IDictionary _typeMap = new Dictionary @@ -562,7 +563,7 @@ namespace Metsys.Bson { if (_string == null && Value != null) { - _string = BitConverter.ToString(Value).Replace("-", string.Empty).ToLower(); + _string = BitConverter.ToString(Value).Replace("-", string.Empty).ToLowerInvariant(); } return _string; @@ -687,6 +688,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal class TypeHelper { private static readonly IDictionary _cachedTypeLookup = new Dictionary(); @@ -787,6 +789,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal class ListWrapper : BaseWrapper { private IList _list; @@ -821,6 +824,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal static class ListHelper { public static Type GetListItemType(Type enumerableType) @@ -865,6 +869,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal class CollectionWrapper : BaseWrapper { private ICollection _list; @@ -892,6 +897,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal abstract class BaseWrapper { public static BaseWrapper Create(Type type, Type itemType, object existingContainer) @@ -948,7 +954,7 @@ namespace Metsys.Bson namespace Metsys.Bson { - + [RequiresUnreferencedCode("Bson uses reflection")] internal class ArrayWrapper : BaseWrapper { private readonly List _list = new List(); @@ -1000,6 +1006,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal class Deserializer { internal class Options diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index 597fab22f8..ab543703d9 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -1,6 +1,8 @@  net6.0;netstandard2.0 + + $(NoWarn);IL2026 @@ -14,4 +16,5 @@ + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs index 728e81b198..eea3d3ad08 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Styling; @@ -31,9 +32,10 @@ namespace Avalonia.Themes.Fluent /// /// Initializes a new instance of the class. /// - public FluentTheme() + /// The parent's service provider. + public FluentTheme(IServiceProvider? sp = null) { - AvaloniaXamlLoader.Load(this); + AvaloniaXamlLoader.Load(sp, this); _baseDark = (IResourceDictionary)GetAndRemove("BaseDark"); _fluentDark = (IResourceDictionary)GetAndRemove("FluentDark"); diff --git a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj index e614dad4d9..1dd6426b39 100644 --- a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj +++ b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj @@ -1,6 +1,8 @@  net6.0;netstandard2.0 + + $(NoWarn);IL2026 @@ -13,4 +15,5 @@ + diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs index af9d305043..56c4cbeac6 100644 --- a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs +++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Styling; @@ -16,9 +17,10 @@ namespace Avalonia.Themes.Simple /// /// Initializes a new instance of the class. /// - public SimpleTheme() + /// The parent's service provider. + public SimpleTheme(IServiceProvider? sp = null) { - AvaloniaXamlLoader.Load(this); + AvaloniaXamlLoader.Load(sp, this); _simpleDark = (IResourceDictionary)GetAndRemove("BaseDark"); _simpleLight = (IResourceDictionary)GetAndRemove("BaseLight"); diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index 621e6fabd7..c762349c1c 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -12,4 +12,5 @@ + diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index 632a7d39a2..4ae1c1599f 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -9,7 +9,7 @@ namespace Avalonia.X11 { class X11IconLoader : IPlatformIconLoader { - IWindowIconImpl LoadIcon(Bitmap bitmap) + static IWindowIconImpl LoadIcon(Bitmap bitmap) { var rv = new X11IconData(bitmap); bitmap.Dispose(); diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 96dc16e186..cbb782edd0 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Controls.Platform; -using Avalonia.Dialogs; using Avalonia.FreeDesktop; using Avalonia.FreeDesktop.DBusIme; using Avalonia.Input; @@ -85,7 +84,7 @@ namespace Avalonia.X11 .Bind().ToConstant(new LinuxMountedVolumeInfoProvider()) .Bind().ToConstant(new X11PlatformLifetimeEvents(this)); - X11Screens = Avalonia.X11.X11Screens.Init(this); + X11Screens = X11.X11Screens.Init(this); Screens = new X11Screens(X11Screens); if (Info.XInputVersion != null) { @@ -143,7 +142,7 @@ namespace Avalonia.X11 throw new NotSupportedException(); } - bool EnableIme(X11PlatformOptions options) + static bool EnableIme(X11PlatformOptions options) { // Disable if explicitly asked by user var avaloniaImModule = Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE"); @@ -164,8 +163,8 @@ namespace Avalonia.X11 return isCjkLocale; } - - bool ShouldUseXim() + + static bool ShouldUseXim() { // Check if we are forbidden from using IME if (Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE") == "none" diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 3f0a6aeb67..7e46606c36 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -34,6 +34,7 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Diagnostics.CodeAnalysis; // ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable IdentifierTypo // ReSharper disable MemberCanBePrivate.Global @@ -654,7 +655,8 @@ namespace Avalonia.X11 { return type.ToString (); } } - + + [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] public static string ToString (object ev) { string result = string.Empty; diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index ca987d0a07..257580a5ec 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/src/Avalonia.X11/X11Window.Ime.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; using Avalonia.FreeDesktop; using Avalonia.Input; using Avalonia.Input.Raw; diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 330494d5dc..2f84e15b32 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; -using System.Reactive.Disposables; using System.Text; using System.Threading.Tasks; using System.Threading; @@ -683,7 +681,7 @@ namespace Avalonia.X11 } - RawInputModifiers TranslateModifiers(XModifierMask state) + static RawInputModifiers TranslateModifiers(XModifierMask state) { var rv = default(RawInputModifiers); if (state.HasAllFlags(XModifierMask.Button1Mask)) diff --git a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj index 4537f8d73e..a2f1b55b6f 100644 --- a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj +++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj index 2564140a03..014d387cb2 100644 --- a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj +++ b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj index 5cebbb6829..366669d10d 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj +++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj @@ -8,5 +8,6 @@ - + + diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj index 1dc7ce5e99..d74cc0b747 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj @@ -1,4 +1,4 @@ - + net6.0;netstandard2.0 @@ -13,6 +13,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index 1ee4402481..2bb91e8a32 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -209,7 +209,7 @@ namespace Avalonia.Markup.Xaml.XamlIl }; var parsedDocuments = new List(); - var rootInstances = new List(); + var originalDocuments = new List(); foreach (var document in documents) { @@ -234,8 +234,8 @@ namespace Avalonia.Markup.Xaml.XamlIl parsedDocuments.Add(new XamlDocumentResource(parsed, document.BaseUri?.ToString(), null, null, builder, compiler.DefinePopulateMethod(builder, parsed, AvaloniaXamlIlCompiler.PopulateName, true), - compiler.DefineBuildMethod(builder, parsed, AvaloniaXamlIlCompiler.BuildName, true))); - rootInstances.Add(document.RootInstance); + document.RootInstance is null ? compiler.DefineBuildMethod(builder, parsed, AvaloniaXamlIlCompiler.BuildName, true) : null)); + originalDocuments.Add(document); } compiler.TransformGroup(parsedDocuments); @@ -251,7 +251,9 @@ namespace Avalonia.Markup.Xaml.XamlIl indexerClosureType.CreateTypeInfo(); trampolineBuilder.CreateTypeInfo(); - return createdTypes.Zip(rootInstances, (l, r) => (l, r)).Select(t => LoadOrPopulate(t.Item1, t.Item2)).ToArray(); + return createdTypes.Zip(originalDocuments, (l, r) => (l, r)) + .Select(t => LoadOrPopulate(t.Item1, t.Item2.RootInstance, t.Item2.ServiceProvider)) + .ToArray(); } static object LoadSreCore(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) @@ -260,7 +262,7 @@ namespace Avalonia.Markup.Xaml.XamlIl } #endif - static object LoadOrPopulate(Type created, object rootInstance) + static object LoadOrPopulate(Type created, object rootInstance, IServiceProvider parentServiceProvider) { var isp = Expression.Parameter(typeof(IServiceProvider)); @@ -271,6 +273,8 @@ namespace Avalonia.Markup.Xaml.XamlIl var populateCb = Expression.Lambda>( Expression.Call(populate, isp, Expression.Convert(epar, populate.GetParameters()[1].ParameterType)), isp, epar).Compile(); + + var serviceProvider = XamlIlRuntimeHelpers.CreateRootServiceProviderV3(parentServiceProvider); if (rootInstance == null) { @@ -282,7 +286,7 @@ namespace Avalonia.Markup.Xaml.XamlIl { overrideField.SetValue(null, new Action( - target => { populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), target); })); + target => { populateCb(serviceProvider, target); })); try { return Activator.CreateInstance(targetType); @@ -296,11 +300,11 @@ namespace Avalonia.Markup.Xaml.XamlIl var createCb = Expression.Lambda>( Expression.Convert(Expression.Call( created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile(); - return createCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2()); + return createCb(serviceProvider); } else { - populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), rootInstance); + populateCb(serviceProvider, rootInstance); return rootInstance; } } @@ -411,7 +415,7 @@ namespace Avalonia.Markup.Xaml.XamlIl var loaded = Assembly.LoadFile(asmPath) .GetTypes().First(x => x.Name == safeUri); _cecilGeneratedCache[safeUri] = loaded; - return LoadOrPopulate(loaded, rootInstance); + return LoadOrPopulate(loaded, rootInstance, null); } #endif } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs index cc29d5ccb5..1bb05e238c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs @@ -163,8 +163,9 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer public bool NeedsParentStack => true; public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) { + codeGen.Ldloc(context.ContextLocal); var method = context.GetAvaloniaTypes().RuntimeHelpers - .FindMethod(m => m.Name == "CreateRootServiceProviderV2"); + .FindMethod(m => m.Name == "CreateRootServiceProviderV3"); codeGen.EmitCall(method); return XamlILNodeEmitResult.Type(0, Type.GetClrType()); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 6c9d510ba0..264361e743 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml; using Avalonia.Markup.Xaml.Parsers; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using Avalonia.Utilities; -using XamlX; using XamlX.Ast; using XamlX.Transform; using XamlX.Transform.Transformers; @@ -67,10 +65,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions string propertyName, IXamlAstTypeReference selectorTypeReference, IXamlLineInfo lineInfo) { XamlAstNamePropertyReference forgedReference; - - var parser = new PropertyParser(); - - var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan())); + + var parsedPropertyName = PropertyParser.Parse(new CharacterReader(propertyName.AsSpan())); if(parsedPropertyName.owner == null) forgedReference = new XamlAstNamePropertyReference(lineInfo, selectorTypeReference, propertyName, selectorTypeReference); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index 491de981dd..5e498f8bcc 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit 491de981dd4433ee58bc9540e2cd4a5d168f8168 +Subproject commit 5e498f8bcca403a34aff5efc825cbb4e12b7fa8e diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 0ab00007e7..c59542c0f3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -6,10 +6,9 @@ $(DefineConstants);RUNTIME_XAML_CECIL False false - CS1591 + $(NoWarn);CS1591 - @@ -69,12 +68,9 @@ + - - - - diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 388ff61400..0b6a2c522f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using Avalonia.Platform; #nullable enable @@ -23,6 +24,17 @@ namespace Avalonia.Markup.Xaml throw new XamlLoadException( $"No precompiled XAML found for {obj.GetType()}, make sure to specify x:Class and include your XAML file as AvaloniaResource"); } + + /// + /// Loads the XAML into a Avalonia component. + /// + /// The parent's service provider. + /// The object to load the XAML into. + public static void Load(IServiceProvider? sp, object obj) + { + throw new XamlLoadException( + $"No precompiled XAML found for {obj.GetType()}, make sure to specify x:Class and include your XAML file as AvaloniaResource"); + } /// /// Loads XAML from a URI. @@ -32,7 +44,23 @@ namespace Avalonia.Markup.Xaml /// A base URI to use if is relative. /// /// The loaded object. + [RequiresUnreferencedCode(TrimmingMessages.AvaloniaXamlLoaderRequiresUnreferenceCodeMessage)] public static object Load(Uri uri, Uri? baseUri = null) + { + return Load(null, uri, baseUri); + } + + /// + /// Loads XAML from a URI. + /// + /// The parent's service provider. + /// The URI of the XAML file. + /// + /// A base URI to use if is relative. + /// + /// The loaded object. + [RequiresUnreferencedCode(TrimmingMessages.AvaloniaXamlLoaderRequiresUnreferenceCodeMessage)] + public static object Load(IServiceProvider? sp, Uri uri, Uri? baseUri = null) { if (uri is null) throw new ArgumentNullException(nameof(uri)); @@ -51,13 +79,25 @@ namespace Avalonia.Markup.Xaml var compiledLoader = assetLocator.GetAssembly(uri, baseUri) ?.GetType("CompiledAvaloniaXaml.!XamlLoader") - ?.GetMethod("TryLoad", new[] {typeof(string)}); + ?.GetMethod("TryLoad", new[] { typeof(System.IServiceProvider), typeof(string) }); if (compiledLoader != null) { - var compiledResult = compiledLoader.Invoke(null, new object[] {absoluteUri.ToString()}); + var compiledResult = compiledLoader.Invoke(null, new object?[] { sp, absoluteUri.ToString()}); if (compiledResult != null) return compiledResult; } + else + { + compiledLoader = assetLocator.GetAssembly(uri, baseUri) + ?.GetType("CompiledAvaloniaXaml.!XamlLoader") + ?.GetMethod("TryLoad", new[] {typeof(string)}); + if (compiledLoader != null) + { + var compiledResult = compiledLoader.Invoke(null, new object?[] {absoluteUri.ToString()}); + if (compiledResult != null) + return compiledResult; + } + } // This is intended for unit-tests only var runtimeLoader = AvaloniaLocator.Current.GetService(); @@ -66,10 +106,9 @@ namespace Avalonia.Markup.Xaml var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); using (var stream = asset.stream) { - return runtimeLoader.Load(new RuntimeXamlLoaderDocument(absoluteUri, stream), new RuntimeXamlLoaderConfiguration - { - LocalAssembly = asset.assembly - }); + var document = new RuntimeXamlLoaderDocument(absoluteUri, stream) { ServiceProvider = sp }; + var configuration = new RuntimeXamlLoaderConfiguration { LocalAssembly = asset.assembly }; + return runtimeLoader.Load(document, configuration); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index 45ca1c4adc..5b660af887 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs @@ -1,9 +1,9 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using Avalonia.Controls; using Avalonia.Logging; -using Avalonia.Markup.Parsers; using Avalonia.Markup.Xaml.Parsers; using Avalonia.Markup.Xaml.Templates; using Avalonia.Styling; @@ -11,6 +11,7 @@ using Avalonia.Utilities; namespace Avalonia.Markup.Xaml.Converters { + [RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)] public class AvaloniaPropertyTypeConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) @@ -21,8 +22,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { var registry = AvaloniaPropertyRegistry.Instance; - var parser = new PropertyParser(); - var (ns, owner, propertyName) = parser.Parse(new CharacterReader(((string)value).AsSpan())); + var (ns, owner, propertyName) = PropertyParser.Parse(new CharacterReader(((string)value).AsSpan())); var ownerType = TryResolveOwnerByName(context, ns, owner); var targetType = context.GetFirstParent()?.TargetType ?? context.GetFirstParent diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceProvider.xaml.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceProvider.xaml.cs new file mode 100644 index 0000000000..42da1604b9 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceProvider.xaml.cs @@ -0,0 +1,16 @@ +using System; +using Avalonia.Controls; +using Avalonia.Styling; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml; + +public class StyleWithServiceProvider : Style +{ + public IServiceProvider ServiceProvider { get; } + + public StyleWithServiceProvider(IServiceProvider sp = null) + { + ServiceProvider = sp; + AvaloniaXamlLoader.Load(sp, this); + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 77a4932ccc..4b32a8cdca 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -9,6 +9,7 @@ using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Markup.Xaml.UnitTests.Xaml; using Avalonia.Media; using Avalonia.Styling; using Avalonia.Threading; @@ -162,7 +163,7 @@ namespace Avalonia.Markup.Xaml.UnitTests } - void AssertThrows(Action callback, Func check) + static void AssertThrows(Action callback, Func check) { try { @@ -314,6 +315,26 @@ namespace Avalonia.Markup.Xaml.UnitTests Assert.NotNull(parsed.ItemTemplate); } + + [Fact] + public void Runtime_Loader_Should_Pass_Parents_From_ServiceProvider() + { + var sp = new TestServiceProvider + { + Parents = new List + { + new UserControl { Resources = { ["Resource1"] = new SolidColorBrush(Colors.Blue) } } + } + }; + var document = new RuntimeXamlLoaderDocument(@" +