Changed all icons of AI Studio to Lucide (#1187)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-11 14:20:49 +01:00
1 parent 9a676101f7
commit 7ef26cd5c8
352 files changed
+6261 -1620

No files matched your search

@@ -20,3 +20,4 @@
MWAIS0014 | Usage | Error | MissingInterpolationAnalyzer
MWAIS0015 | Usage | Error | JsonSourceGenerationAnalyzer
MWAIS0016 | Style | Error | ClassicExtensionMethodAnalyzer
MWAIS0017 | Usage | Error | IconCatalogAnalyzer
@@ -18,4 +18,5 @@ public static class Identifier
public const string MISSING_INTERPOLATION_ANALYZER = $"{Tools.ID_PREFIX}0014";
public const string JSON_SOURCE_GENERATION_ANALYZER = $"{Tools.ID_PREFIX}0015";
public const string CLASSIC_EXTENSION_METHOD_ANALYZER = $"{Tools.ID_PREFIX}0016";
public const string ICON_CATALOG_ANALYZER = $"{Tools.ID_PREFIX}0017";
}
@@ -0,0 +1,99 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace SourceCodeRules.UsageAnalyzers;
/// <summary>
/// Refuses icons taken from anywhere but the catalog AppIcons.
/// </summary>
/// <remarks>
/// AppIcons names every icon after its meaning in AI Studio and decides there which Lucide icon stands for it.
/// A component which picks an icon of LucideIcons or of MudBlazor on its own bypasses that decision: the same
/// meaning ends up with different icons, and an icon of another set looks out of place next to all the others.
/// </remarks>
#pragma warning disable RS1038
[DiagnosticAnalyzer(LanguageNames.CSharp)]
#pragma warning restore RS1038
public sealed class IconCatalogAnalyzer : DiagnosticAnalyzer
{
private const string DIAGNOSTIC_ID = Identifier.ICON_CATALOG_ANALYZER;
private const string TITLE = "Icons come only from AppIcons";
private const string MESSAGE_FORMAT = "Use the constant of AppIcons for what the icon means instead of '{0}'; when there is none yet, add one to AppIcons";
private const string DESCRIPTION = "AppIcons in Tools/AppIcons.cs is the one place which picks the icons of the app, so every meaning keeps one icon and every icon stays a glyph of the Lucide font.";
private const string CATEGORY = "Usage";
/// <summary>
/// The icon sets which only the catalog may use: MudBlazor's icons with all their nested classes, and the Lucide icons.
/// </summary>
private static readonly ImmutableHashSet<string> ICON_SETS = ImmutableHashSet.Create("MudBlazor.Icons", "AIStudio.Tools.LucideIcons");
/// <summary>
/// The types which may use the icon sets: the catalog itself and the two classes LucideIconGenerator writes.
/// </summary>
private static readonly ImmutableHashSet<string> ALLOWED_TYPES = ImmutableHashSet.Create("AIStudio.Tools.AppIcons", "AIStudio.Tools.LucideIcons", "AIStudio.Tools.LucideIconRegistry");
private static readonly DiagnosticDescriptor RULE = new(DIAGNOSTIC_ID, TITLE, MESSAGE_FORMAT, CATEGORY, DiagnosticSeverity.Error, isEnabledByDefault: true, description: DESCRIPTION);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [RULE];
public override void Initialize(AnalysisContext context)
{
//
// Razor markup ends up in generated code, and most icons are set there, e.g. Icon="@Icons.Material.Filled.Add".
// We analyze generated code as well, and the Razor compiler maps the diagnostic back to the line of the .razor
// file. The classes of LucideIconGenerator are generated, too; ALLOWED_TYPES lets them through:
//
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterOperationAction(AnalyzeFieldReference, OperationKind.FieldReference);
}
private static void AnalyzeFieldReference(OperationAnalysisContext context)
{
var reference = (IFieldReferenceOperation)context.Operation;
if (!ICON_SETS.Contains(OutermostType(reference.Field.ContainingType).ToDisplayString()))
return;
if (IsAllowedType(context.ContainingSymbol))
return;
context.ReportDiagnostic(Diagnostic.Create(RULE, reference.Syntax.GetLocation(), reference.Field.ToDisplayString()));
}
/// <summary>
/// The type which encloses the given one and is itself not nested, e.g. Icons for Icons.Material.Filled.
/// </summary>
/// <param name="type">The type to start from.</param>
private static INamedTypeSymbol OutermostType(INamedTypeSymbol type)
{
while (type.ContainingType is { } containingType)
type = containingType;
return type;
}
/// <summary>
/// Whether the analyzed code belongs to a type which may use the icon sets, or to a type nested in one.
/// </summary>
/// <param name="containingSymbol">The symbol containing the analyzed code, e.g. a method or a constant.</param>
private static bool IsAllowedType(ISymbol? containingSymbol)
{
var containingType = containingSymbol as INamedTypeSymbol ?? containingSymbol?.ContainingType;
while (containingType is not null)
{
if (ALLOWED_TYPES.Contains(containingType.ToDisplayString()))
return true;
containingType = containingType.ContainingType;
}
return false;
}
}