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
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class AllAnalyzersIntegrationTests
"BWFC021", // FindControlUsageAnalyzer
"BWFC022", // PageClientScriptUsageAnalyzer
"BWFC023", // IPostBackEventHandlerUsageAnalyzer
"BWFC025", // NonSerializableViewStateAnalyzer
};

#region ID Registration and Uniqueness
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ protected void DoInit() { }
";

private static DiagnosticResult ExpectBWFC003(string memberName) =>
new DiagnosticResult(IsPostBackUsageAnalyzer.DiagnosticId, DiagnosticSeverity.Warning)
new DiagnosticResult(IsPostBackUsageAnalyzer.DiagnosticId, DiagnosticSeverity.Info)
.WithArguments(memberName);

#region Positive cases — BWFC003 SHOULD fire
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;

namespace BlazorWebFormsComponents.Analyzers.Test;

using AnalyzerTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest<
NonSerializableViewStateAnalyzer,
DefaultVerifier>;

/// <summary>
/// Tests for BWFC025: ViewState value may not be JSON-serializable.
/// </summary>
public class NonSerializableViewStateAnalyzerTests
{
private const string StubSource = @"
using System;
using System.Collections.Generic;

public class ViewStateDictionary : Dictionary<string, object>
{
public void Set<T>(string key, T value) { this[key] = value; }
}

public class PageBase
{
public ViewStateDictionary ViewState { get; } = new ViewStateDictionary();
}
";

private static DiagnosticResult ExpectBWFC025(string typeName) =>
new DiagnosticResult(NonSerializableViewStateAnalyzer.DiagnosticId, DiagnosticSeverity.Warning)
.WithArguments(typeName);

#region Negative cases — BWFC025 should NOT fire

[Fact]
public async Task ViewState_AssignString_NoDiagnostic()
{
var source = @"
public class MyPage : PageBase
{
public void Page_Load()
{
ViewState[""key""] = ""hello"";
}
}";

var test = new AnalyzerTest
{
TestState = { Sources = { source, StubSource } }
};
await test.RunAsync();
}

[Fact]
public async Task ViewState_AssignInt_NoDiagnostic()
{
var source = @"
public class MyPage : PageBase
{
public void Page_Load()
{
ViewState[""key""] = 42;
}
}";

var test = new AnalyzerTest
{
TestState = { Sources = { source, StubSource } }
};
await test.RunAsync();
}

[Fact]
public async Task ViewState_AssignBool_NoDiagnostic()
{
var source = @"
public class MyPage : PageBase
{
public void Page_Load()
{
ViewState[""key""] = true;
}
}";

var test = new AnalyzerTest
{
TestState = { Sources = { source, StubSource } }
};
await test.RunAsync();
}

[Fact]
public async Task ViewState_SetGeneric_String_NoDiagnostic()
{
var source = @"
public class MyPage : PageBase
{
public void Page_Load()
{
ViewState.Set<string>(""key"", ""val"");
}
}";

var test = new AnalyzerTest
{
TestState = { Sources = { source, StubSource } }
};
await test.RunAsync();
}

#endregion

#region Positive cases — BWFC025 SHOULD fire

[Fact]
public async Task ViewState_AssignDisposable_Diagnostic()
{
var source = @"
using System.IO;

public class MyPage : PageBase
{
public void Page_Load()
{
{|#0:ViewState[""key""] = new MemoryStream()|};
}
}";

var test = new AnalyzerTest
{
TestState = { Sources = { source, StubSource } },
ExpectedDiagnostics = { ExpectBWFC025("System.IO.MemoryStream").WithLocation(0) }
};
await test.RunAsync();
}

[Fact]
public async Task ViewState_AssignDataTable_Diagnostic()
{
// Use a minimal stub for DataTable since System.Data is not referenced
var dataStub = @"
namespace System.Data
{
public class DataTable
{
public string TableName { get; set; }
}
}
";

var source = @"
using System.Data;

public class MyPage : PageBase
{
public void Page_Load()
{
{|#0:ViewState[""key""] = new DataTable()|};
}
}";

var test = new AnalyzerTest
{
TestState = { Sources = { source, StubSource, dataStub } },
ExpectedDiagnostics = { ExpectBWFC025("System.Data.DataTable").WithLocation(0) }
};
await test.RunAsync();
}

[Fact]
public async Task ViewState_AssignDelegate_Diagnostic()
{
var source = @"
using System;

public class MyPage : PageBase
{
public void Page_Load()
{
Action callback = () => { };
{|#0:ViewState[""key""] = callback|};
}
}";

var test = new AnalyzerTest
{
TestState = { Sources = { source, StubSource } },
ExpectedDiagnostics = { ExpectBWFC025("System.Action").WithLocation(0) }
};
await test.RunAsync();
}

[Fact]
public async Task ThisViewState_AssignDisposable_Diagnostic()
{
var source = @"
using System.IO;

public class MyPage : PageBase
{
public void Page_Load()
{
{|#0:this.ViewState[""key""] = new MemoryStream()|};
}
}";

var test = new AnalyzerTest
{
TestState = { Sources = { source, StubSource } },
ExpectedDiagnostics = { ExpectBWFC025("System.IO.MemoryStream").WithLocation(0) }
};
await test.RunAsync();
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class PageBase
";

private static DiagnosticResult ExpectBWFC002(string memberName) =>
new DiagnosticResult(ViewStateUsageAnalyzer.DiagnosticId, DiagnosticSeverity.Warning)
new DiagnosticResult(ViewStateUsageAnalyzer.DiagnosticId, DiagnosticSeverity.Info)
.WithArguments(memberName);

#region Positive cases — BWFC002 SHOULD fire
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
BWFC001 | Usage | Warning | MissingParameterAttributeAnalyzer
BWFC002 | Usage | Warning | ViewStateUsageAnalyzer
BWFC003 | Usage | Warning | IsPostBackUsageAnalyzer
BWFC002 | Usage | Info | ViewStateUsageAnalyzer (was Warning — ViewState now works as migration shim)
BWFC003 | Usage | Info | IsPostBackUsageAnalyzer (was Warning — IsPostBack now works via BWFC shim)
BWFC004 | Usage | Warning | ResponseRedirectAnalyzer
BWFC005 | Usage | Warning | SessionUsageAnalyzer
BWFC010 | Usage | Info | RequiredAttributeAnalyzer
Expand All @@ -16,3 +16,4 @@ BWFC020 | Migration | Info | ViewStatePropertyPatternAnalyzer
BWFC021 | Migration | Warning | FindControlUsageAnalyzer
BWFC022 | Migration | Warning | PageClientScriptUsageAnalyzer
BWFC023 | Migration | Warning | IPostBackEventHandlerUsageAnalyzer
BWFC025 | Usage | Warning | NonSerializableViewStateAnalyzer
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,24 @@ namespace BlazorWebFormsComponents.Analyzers
{
/// <summary>
/// Analyzer that detects IsPostBack and Page.IsPostBack usage patterns.
/// Blazor uses component lifecycle instead of the postback model.
/// IsPostBack works via the BWFC shim; suggests Blazor lifecycle methods for new code.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class IsPostBackUsageAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "BWFC003";

private static readonly LocalizableString Title = "IsPostBack usage detected";
private static readonly LocalizableString MessageFormat = "'{0}' checks IsPostBack \u2014 Blazor doesn't have postbacks. Use lifecycle methods (OnInitialized, OnParametersSet) instead.";
private static readonly LocalizableString Description = "Flags IsPostBack and Page.IsPostBack checks. Blazor uses component lifecycle instead of postback model.";
private static readonly LocalizableString MessageFormat = "'{0}' checks IsPostBack \u2014 this works via the BWFC shim (SSR: checks HTTP POST, Interactive: tracks initialization). For new code, prefer OnInitialized lifecycle.";
private static readonly LocalizableString Description = "Flags IsPostBack and Page.IsPostBack checks. IsPostBack works via the BWFC shim during migration; prefer Blazor lifecycle methods for new code.";
private const string Category = "Usage";

private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
DiagnosticId,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Warning,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: Description);

Expand Down
Loading
Loading