Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,4 @@ MigrationBackup/
.ionide/
.tools
docs/memberpage.2.58.0
.dotnet
27 changes: 27 additions & 0 deletions docs/rules/WinUIEx1003.md
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<Authors>Morten Nielsen - https://xaml.dev</Authors>
<Company>Morten Nielsen - https://xaml.dev</Company>
<PackageIcon>logo.png</PackageIcon>
<Version>2.9.0</Version>
<Version>2.9.1</Version>
</PropertyGroup>

<ItemGroup Condition="'$(PackageId)'!=''">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;

namespace WinUIEx.Analyzers.Test
{
[TestClass]
public class WinUIExFrameNavigateAnalyzerTests : BaseAnalyzersUnitTest<WinUIEx.Analyzers.WinUIExFrameNavigateAnalyzer, WinUIEx.Analyzers.WinUIExAnalyzersCodeFixProvider>
{
[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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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)
27 changes: 27 additions & 0 deletions src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion src/WinUIEx.Analyzers/WinUIEx.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,13 @@
<data name="DispatcherTitle" xml:space="preserve">
<value>Dispatcher must be replaced with DispatcherQueue</value>
</data>
</root>
<data name="NavigateTypeDescription" xml:space="preserve">
<value>Frame.Navigate only supports page types that inherit from Microsoft.UI.Xaml.Controls.Page.</value>
</data>
<data name="NavigateTypeMessageFormat" xml:space="preserve">
<value>Type '{0}' cannot be used with Frame.Navigate because it does not inherit from '{1}'.</value>
</data>
<data name="NavigateTypeTitle" xml:space="preserve">
<value>Frame.Navigate target must inherit from Page</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -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,
Comment thread
dotMorten marked this conversation as resolved.
isEnabledByDefault: true,
description: Description,
helpLinkUri: "https://dotmorten.github.io/WinUIEx/rules/WinUIEx1003.html");

public override ImmutableArray<DiagnosticDescriptor> 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;
}
}
}
3 changes: 1 addition & 2 deletions src/WinUIEx/WinUIEx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
<PackageId>WinUIEx</PackageId>
<Product>WinUI Extensions</Product>
<PackageReleaseNotes>
- 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
</PackageReleaseNotes>
</PropertyGroup>

Expand Down
Loading