diff --git a/.gitignore b/.gitignore
index 9119864d..4de78ccb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -350,3 +350,4 @@ MigrationBackup/
.ionide/
.tools
docs/memberpage.2.58.0
+.dotnet
diff --git a/docs/rules/WinUIEx1003.md b/docs/rules/WinUIEx1003.md
new file mode 100644
index 00000000..7628d6f6
--- /dev/null
+++ b/docs/rules/WinUIEx1003.md
@@ -0,0 +1,27 @@
+## WinUIEX1003: Frame.Navigate target must inherit from Page.
+
+`Frame.Navigate(Type)` only supports types that inherit from `Microsoft.UI.Xaml.Controls.Page`. Passing any other type will fail at runtime, so the analyzer reports this as a build error.
+
+|Item|Value|
+|-|-|
+|Category|Usage|
+|Enabled|True|
+|Severity|Error|
+|CodeFix|False|
+---
+
+### Example
+
+This will trigger the analyzer:
+```cs
+frame.Navigate(typeof(MyViewModel));
+```
+
+Use a page type instead:
+```cs
+frame.Navigate(typeof(MyPage));
+```
+
+### References
+
+ - [Frame.Navigate(Type) Method](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.frame.navigate)
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 7b053627..50fca1a0 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -17,7 +17,7 @@
Morten Nielsen - https://xaml.dev
Morten Nielsen - https://xaml.dev
logo.png
- 2.9.0
+ 2.9.1
diff --git a/src/WinUIEx.Analyzers/WinUIEx.Analyzers.Test/WinUIExFrameNavigateAnalyzerTests.cs b/src/WinUIEx.Analyzers/WinUIEx.Analyzers.Test/WinUIExFrameNavigateAnalyzerTests.cs
new file mode 100644
index 00000000..5b0702b2
--- /dev/null
+++ b/src/WinUIEx.Analyzers/WinUIEx.Analyzers.Test/WinUIExFrameNavigateAnalyzerTests.cs
@@ -0,0 +1,71 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Threading.Tasks;
+
+namespace WinUIEx.Analyzers.Test
+{
+ [TestClass]
+ public class WinUIExFrameNavigateAnalyzerTests : BaseAnalyzersUnitTest
+ {
+ [TestMethod]
+ public async Task Frame_Navigate_With_NonPage_Type()
+ {
+ var testCode = @"
+ using System;
+ using Microsoft.UI.Xaml.Controls;
+ namespace ConsoleApplication1
+ {
+ class MyViewModel { }
+
+ class MyClass
+ {
+ public void MethodName(Frame frame)
+ {
+ frame.Navigate({|#0:typeof(MyViewModel)|});
+ }
+ }
+ }";
+ var expected = Diagnostic("WinUIEx1003").WithLocation(0).WithArguments("ConsoleApplication1.MyViewModel", "Microsoft.UI.Xaml.Controls.Page");
+ await VerifyAnalyzerAsync(testCode, expected);
+ }
+
+ [TestMethod]
+ public async Task Frame_Navigate_With_Page_Subclass()
+ {
+ var testCode = @"
+ using System;
+ using Microsoft.UI.Xaml.Controls;
+ namespace ConsoleApplication1
+ {
+ class MyPage : Page { }
+
+ class MyClass
+ {
+ public void MethodName(Frame frame)
+ {
+ frame.Navigate(typeof(MyPage));
+ }
+ }
+ }";
+ await VerifyAnalyzerAsync(testCode);
+ }
+
+ [TestMethod]
+ public async Task Frame_Navigate_With_Page_Base_Type()
+ {
+ var testCode = @"
+ using System;
+ using Microsoft.UI.Xaml.Controls;
+ namespace ConsoleApplication1
+ {
+ class MyClass
+ {
+ public void MethodName(Frame frame)
+ {
+ frame.Navigate(typeof(Page));
+ }
+ }
+ }";
+ await VerifyAnalyzerAsync(testCode);
+ }
+ }
+}
diff --git a/src/WinUIEx.Analyzers/WinUIEx.Analyzers/AnalyzerReleases.Shipped.md b/src/WinUIEx.Analyzers/WinUIEx.Analyzers/AnalyzerReleases.Shipped.md
index 6af735d0..f58691a9 100644
--- a/src/WinUIEx.Analyzers/WinUIEx.Analyzers/AnalyzerReleases.Shipped.md
+++ b/src/WinUIEx.Analyzers/WinUIEx.Analyzers/AnalyzerReleases.Shipped.md
@@ -5,4 +5,5 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
WinUIEx1001 | Usage | Warning | The member will always be null, [Documentation](https://dotmorten.github.io/WinUIEx/rules/WinUIEx1001.html)
-WinUIEx1002 | Usage | Warning | Dispatcher must be replaced with DispatcherQueue, [Documentation](https://dotmorten.github.io/WinUIEx/rules/WinUIEx1002.html)
\ No newline at end of file
+WinUIEx1002 | Usage | Warning | Dispatcher must be replaced with DispatcherQueue, [Documentation](https://dotmorten.github.io/WinUIEx/rules/WinUIEx1002.html)
+WinUIEx1003 | Usage | Error | Frame.Navigate target must inherit from Page, [Documentation](https://dotmorten.github.io/WinUIEx/rules/WinUIEx1003.html)
diff --git a/src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.Designer.cs b/src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.Designer.cs
index 7de89087..212989cd 100644
--- a/src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.Designer.cs
+++ b/src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.Designer.cs
@@ -113,5 +113,32 @@ internal static string DispatcherTitle {
return ResourceManager.GetString("DispatcherTitle", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Frame.Navigate only supports page types that inherit from Microsoft.UI.Xaml.Controls.Page..
+ ///
+ internal static string NavigateTypeDescription {
+ get {
+ return ResourceManager.GetString("NavigateTypeDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Type '{0}' cannot be used with Frame.Navigate because it does not inherit from '{1}'..
+ ///
+ internal static string NavigateTypeMessageFormat {
+ get {
+ return ResourceManager.GetString("NavigateTypeMessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Frame.Navigate target must inherit from Page.
+ ///
+ internal static string NavigateTypeTitle {
+ get {
+ return ResourceManager.GetString("NavigateTypeTitle", resourceCulture);
+ }
+ }
}
}
diff --git a/src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.resx b/src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.resx
index db7b2699..698d4148 100644
--- a/src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.resx
+++ b/src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.resx
@@ -135,4 +135,13 @@
Dispatcher must be replaced with DispatcherQueue
-
\ No newline at end of file
+
+ Frame.Navigate only supports page types that inherit from Microsoft.UI.Xaml.Controls.Page.
+
+
+ Type '{0}' cannot be used with Frame.Navigate because it does not inherit from '{1}'.
+
+
+ Frame.Navigate target must inherit from Page
+
+
diff --git a/src/WinUIEx.Analyzers/WinUIEx.Analyzers/WinUIExFrameNavigateAnalyzer.cs b/src/WinUIEx.Analyzers/WinUIEx.Analyzers/WinUIExFrameNavigateAnalyzer.cs
new file mode 100644
index 00000000..708dbd82
--- /dev/null
+++ b/src/WinUIEx.Analyzers/WinUIEx.Analyzers/WinUIExFrameNavigateAnalyzer.cs
@@ -0,0 +1,79 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+using System.Collections.Immutable;
+
+namespace WinUIEx.Analyzers
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class WinUIExFrameNavigateAnalyzer : DiagnosticAnalyzer
+ {
+ public const string DiagnosticId1003 = "WinUIEx1003";
+
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.NavigateTypeTitle), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.NavigateTypeMessageFormat), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.NavigateTypeDescription), Resources.ResourceManager, typeof(Resources));
+ private const string Category = "Usage";
+
+ private static readonly DiagnosticDescriptor NavigateTypeRule = new DiagnosticDescriptor(
+ DiagnosticId1003,
+ Title,
+ MessageFormat,
+ Category,
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: Description,
+ helpLinkUri: "https://dotmorten.github.io/WinUIEx/rules/WinUIEx1003.html");
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(NavigateTypeRule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterOperationAction(AnalyzeInvocationOperation, OperationKind.Invocation);
+ }
+
+ private void AnalyzeInvocationOperation(OperationAnalysisContext context)
+ {
+ var invocation = context.Operation as IInvocationOperation;
+ if (invocation == null || !IsFrameNavigateWithTypeParameter(invocation.TargetMethod) || invocation.Arguments.Length == 0)
+ return;
+
+ var typeOfArgument = invocation.Arguments[0].Value as ITypeOfOperation;
+ if (typeOfArgument == null || typeOfArgument.TypeOperand == null)
+ return;
+
+ var pageType = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Controls.Page");
+ if (pageType == null || InheritsFrom(typeOfArgument.TypeOperand, pageType))
+ return;
+
+ var diagnostic = Diagnostic.Create(
+ NavigateTypeRule,
+ invocation.Arguments[0].Syntax.GetLocation(),
+ typeOfArgument.TypeOperand.ToDisplayString(),
+ pageType.ToDisplayString());
+ context.ReportDiagnostic(diagnostic);
+ }
+
+ private static bool IsFrameNavigateWithTypeParameter(IMethodSymbol method)
+ {
+ return method != null &&
+ method.Name == "Navigate" &&
+ method.ContainingType?.ToDisplayString() == "Microsoft.UI.Xaml.Controls.Frame" &&
+ method.Parameters.Length > 0 &&
+ method.Parameters[0].Type.ToDisplayString() == "System.Type";
+ }
+
+ private static bool InheritsFrom(ITypeSymbol type, ITypeSymbol baseType)
+ {
+ for (var current = type; current != null; current = current.BaseType)
+ {
+ if (SymbolEqualityComparer.Default.Equals(current, baseType))
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/WinUIEx/WinUIEx.csproj b/src/WinUIEx/WinUIEx.csproj
index e3f2d82b..49edd037 100644
--- a/src/WinUIEx/WinUIEx.csproj
+++ b/src/WinUIEx/WinUIEx.csproj
@@ -25,8 +25,7 @@
WinUIEx
WinUI Extensions
- - Added support for WinUI 3 based flyouts in the system tray.
- - Added extension method for assigning transparent regions to a window (Issue #235).
+ - Added a new analyzer to call out when an invalid type is passed to `Frame.Navigate`. #260