Browse Source
* feat: add bitmap analyzer. * feat: add code fix provider. * feat: use actual name. add comment.pull/18251/head
committed by
GitHub
3 changed files with 184 additions and 0 deletions
@ -0,0 +1,68 @@ |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|||
using Microsoft.CodeAnalysis.Diagnostics; |
|||
|
|||
namespace Avalonia.Analyzers; |
|||
|
|||
/// <summary>
|
|||
/// Analyzes object creation expressions to detect instances where a Bitmap is initialized
|
|||
/// from the "avares" scheme directly, which is not allowed. Instead, the AssetLoader should be used
|
|||
/// to open assets as a stream first.
|
|||
/// </summary>
|
|||
[DiagnosticAnalyzer(LanguageNames.CSharp)] |
|||
public class BitmapAnalyzer: DiagnosticAnalyzer |
|||
{ |
|||
public const string DiagnosticId = "AVA2002"; |
|||
private const string Title = "Cannot initialize Bitmap from \"avares\" scheme"; |
|||
private const string MessageFormat = "Cannot initialize Bitmap from \"avares\" scheme directly"; |
|||
private const string Description = "Cannot initialize Bitmap from \"avares\" scheme, use AssetLoader to open assets as stream first."; |
|||
private const string Category = "Usage"; |
|||
|
|||
private static readonly DiagnosticDescriptor _rule = new( |
|||
DiagnosticId, |
|||
Title, |
|||
MessageFormat, |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
description: Description); |
|||
|
|||
/// <inheritdoc />
|
|||
public override void Initialize(AnalysisContext context) |
|||
{ |
|||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); |
|||
context.EnableConcurrentExecution(); |
|||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ObjectCreationExpression); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(_rule); } } |
|||
|
|||
private static void AnalyzeNode(SyntaxNodeAnalysisContext context) |
|||
{ |
|||
var objectCreation = (ObjectCreationExpressionSyntax)context.Node; |
|||
var semanticModel = context.SemanticModel; |
|||
|
|||
// Check if the object creation is creating an instance of Avalonia.Media.Imaging.Bitmap
|
|||
var symbol = semanticModel.GetSymbolInfo(objectCreation).Symbol as IMethodSymbol; |
|||
if (symbol == null || symbol.ContainingType.ToString() != "Avalonia.Media.Imaging.Bitmap") |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Check if any argument starts with "avares://"
|
|||
foreach (var argument in objectCreation.ArgumentList.Arguments) |
|||
{ |
|||
var constantValue = semanticModel.GetConstantValue(argument.Expression); |
|||
if (constantValue.HasValue && constantValue.Value is string stringValue && stringValue.StartsWith("avares://")) |
|||
{ |
|||
var diagnostic = Diagnostic.Create(_rule, objectCreation.GetLocation()); |
|||
context.ReportDiagnostic(diagnostic); |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Composition; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CodeActions; |
|||
using Microsoft.CodeAnalysis.CodeFixes; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|||
|
|||
namespace Avalonia.Analyzers; |
|||
|
|||
/// <summary>
|
|||
/// Provides a code fix for the BitmapAnalyzer diagnostic, which replaces "avares://" string arguments
|
|||
/// with a call to AssetLoader.Open(new Uri("avares://...")).
|
|||
/// </summary>
|
|||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(BitmapAnalyzerCSCodeFixProvider))] |
|||
[Shared] |
|||
public class BitmapAnalyzerCSCodeFixProvider : CodeFixProvider |
|||
{ |
|||
private const string _title = "Use AssetLoader to open assets as stream first"; |
|||
|
|||
/// <inheritdoc />
|
|||
public override ImmutableArray<string> FixableDiagnosticIds { get; } = |
|||
ImmutableArray.Create(BitmapAnalyzer.DiagnosticId); |
|||
|
|||
/// <inheritdoc />
|
|||
public override FixAllProvider? GetFixAllProvider() |
|||
{ |
|||
return WellKnownFixAllProviders.BatchFixer; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) |
|||
{ |
|||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); |
|||
|
|||
var diagnostic = context.Diagnostics.First(); |
|||
var diagnosticSpan = diagnostic.Location.SourceSpan; |
|||
|
|||
// Find the type declaration identified by the diagnostic.
|
|||
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf() |
|||
.OfType<LocalDeclarationStatementSyntax>().First(); |
|||
|
|||
// Register a code action that will invoke the fix.
|
|||
context.RegisterCodeFix( |
|||
CodeAction.Create( |
|||
_title, |
|||
c => ReplaceArgumentAsync(context.Document, declaration, c), |
|||
_title), |
|||
diagnostic); |
|||
} |
|||
|
|||
private async Task<Document> ReplaceArgumentAsync(Document contextDocument, LocalDeclarationStatementSyntax declaration, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
var root = await contextDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); |
|||
var semanticModel = await contextDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); |
|||
|
|||
var objectCreation = declaration.DescendantNodes().OfType<ObjectCreationExpressionSyntax>().First(); |
|||
var argumentList = objectCreation.ArgumentList; |
|||
var newArguments = argumentList.Arguments.Select(arg => |
|||
{ |
|||
var constantValue = semanticModel.GetConstantValue(arg.Expression); |
|||
if (constantValue.HasValue && constantValue.Value is string stringValue && |
|||
stringValue.StartsWith("avares://")) |
|||
{ |
|||
var newArgument = SyntaxFactory.Argument( |
|||
SyntaxFactory.InvocationExpression( |
|||
SyntaxFactory.MemberAccessExpression( |
|||
SyntaxKind.SimpleMemberAccessExpression, |
|||
SyntaxFactory.IdentifierName("AssetLoader"), |
|||
SyntaxFactory.IdentifierName("Open"))) |
|||
.WithArgumentList( |
|||
SyntaxFactory.ArgumentList( |
|||
SyntaxFactory.SingletonSeparatedList( |
|||
SyntaxFactory.Argument( |
|||
SyntaxFactory.ObjectCreationExpression( |
|||
SyntaxFactory.IdentifierName("Uri")) |
|||
.WithArgumentList( |
|||
SyntaxFactory.ArgumentList( |
|||
SyntaxFactory.SingletonSeparatedList( |
|||
SyntaxFactory.Argument( |
|||
SyntaxFactory.LiteralExpression( |
|||
SyntaxKind.StringLiteralExpression, |
|||
SyntaxFactory |
|||
.Literal(stringValue))))))))))); |
|||
return newArgument; |
|||
} |
|||
|
|||
return arg; |
|||
}).ToArray(); |
|||
|
|||
var newArgumentList = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(newArguments)); |
|||
var newObjectCreation = objectCreation.WithArgumentList(newArgumentList); |
|||
var newRoot = root.ReplaceNode(objectCreation, newObjectCreation); |
|||
|
|||
var usingDirective = ((CompilationUnitSyntax)newRoot).Usings; |
|||
var newUsings = new List<UsingDirectiveSyntax>(); |
|||
if(!usingDirective.Any(a=>a.Name.ToString().Contains("Avalonia.Platform"))) |
|||
{ |
|||
newUsings.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Avalonia.Platform"))); |
|||
} |
|||
if(!usingDirective.Any(a=>a.Name.ToString().Contains("System"))) |
|||
{ |
|||
newUsings.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))); |
|||
} |
|||
// Add the new using directives to the root
|
|||
newRoot = ((CompilationUnitSyntax)newRoot).AddUsings(newUsings.ToArray()); |
|||
|
|||
return contextDocument.WithSyntaxRoot(newRoot); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue