Removed provider access rule suppressions outside the settings manager (#915)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-08-12 20:44:48 +02:00
1 parent 688fea73cb
commit ede45103b9
11 files changed
+254 -122

No files matched your search

@@ -17,11 +17,16 @@ public sealed class ProviderAccessAnalyzer : DiagnosticAnalyzer
private static readonly string TITLE = "Direct access to `Providers` is not allowed";
private static readonly string MESSAGE_FORMAT = "Direct access to `SettingsManager.ConfigurationData.Providers` is not allowed. Instead, use APIs like `SettingsManager.GetPreselectedProvider`, etc.";
private static readonly string MESSAGE_FORMAT = "Direct access to `SettingsManager.ConfigurationData.Providers` is not allowed. Instead, use APIs like `SettingsManager.GetAllProviders`, `GetProviderById`, `GetConfidentProviders`, `GetPreselectedProvider`, or `GetChatProviderForLoadedChat`.";
private static readonly string DESCRIPTION = MESSAGE_FORMAT;
private const string CATEGORY = "Usage";
/// <summary>
/// The one type which owns the provider list and is therefore allowed to access it directly.
/// </summary>
private const string OWNING_TYPE = "AIStudio.Settings.SettingsManager";
private static readonly DiagnosticDescriptor RULE = new(DIAGNOSTIC_ID, TITLE, MESSAGE_FORMAT, CATEGORY, DiagnosticSeverity.Error, isEnabledByDefault: true, description: DESCRIPTION);
@@ -29,7 +34,12 @@ public sealed class ProviderAccessAnalyzer : DiagnosticAnalyzer
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
//
// We analyze generated code as well, because Razor markup ends up in generated files. Without
// this, any `ConfigurationData.Providers` access written directly in a `.razor` file would
// bypass this rule entirely. The Razor compiler maps the diagnostic back to the `.razor` line:
//
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(this.AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression);
}
@@ -42,8 +52,17 @@ public sealed class ProviderAccessAnalyzer : DiagnosticAnalyzer
if (memberAccess.Name.Identifier.Text != "Providers")
return;
//
// The settings manager owns the provider list: it implements the very APIs which all other
// code is meant to use, so it must access `Providers` directly. Exempting it here keeps
// those implementations free of suppression attributes, which would otherwise read as if
// suppressing this rule was a normal thing to do:
//
if (IsOwningType(context.ContainingSymbol))
return;
// Get the full path of the member access:
var fullPath = this.GetFullMemberAccessPath(memberAccess);
var fullPath = GetFullMemberAccessPath(memberAccess);
// Check for the forbidden pattern:
if (fullPath.EndsWith("ConfigurationData.Providers"))
@@ -53,7 +72,30 @@ public sealed class ProviderAccessAnalyzer : DiagnosticAnalyzer
}
}
private string GetFullMemberAccessPath(ExpressionSyntax expression)
/// <summary>
/// Checks whether the analyzed node sits inside the type which owns the provider list.
/// </summary>
/// <remarks>
/// The containing symbol is the member the node belongs to, e.g. a method or a property. We walk
/// the chain of containing types so that nested types of the owning type are covered as well.
/// </remarks>
/// <param name="containingSymbol">The symbol containing the analyzed node, which may be null.</param>
/// <returns>True, when the node belongs to the owning type.</returns>
private static bool IsOwningType(ISymbol? containingSymbol)
{
var containingType = containingSymbol as INamedTypeSymbol ?? containingSymbol?.ContainingType;
while (containingType != null)
{
if (containingType.ToDisplayString() == OWNING_TYPE)
return true;
containingType = containingType.ContainingType;
}
return false;
}
private static string GetFullMemberAccessPath(ExpressionSyntax expression)
{
var parts = new List<string>();
while (expression is MemberAccessExpressionSyntax memberAccess)