Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 invalid type as a parameter for `Frame.Navigate`. #260
Comment thread
dotMorten marked this conversation as resolved.
Outdated
</PackageReleaseNotes>
</PropertyGroup>

Expand Down
Loading