-
Notifications
You must be signed in to change notification settings - Fork 536
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Microsoft.Android.Sdk.Analysis] Warn on missing activation ctors (#9447
) Fixes: #8410 Context: b3079db Context: https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix Commit b3079db added a `Microsoft.Android.Sdk.Analysis.dll` assembly which contained a [`DiagnosticAnalyzer`][0] to suppress IDE0002 warnings. Extend that support to add a `CustomApplicationAnalyzer` which checks for the [`(IntPtr, JniHandleOwnership)`][1] constructor on [`Android.App.Application`][2] subclasses, as this constructor is currently required. If the constructor is missing: [Application] public class MyApp : Application { } then the app will crash during startup: System.NotSupportedException: Unable to activate instance of type MyApp from native handle 0x7ff1f3b468 (key_handle 0x466b26f). If we find an `Application` subclass that is missing the required constructor, we'll emit a DNAA0001 warning: warning DNAA0001: Application class 'MyApp' does not have an Activation Constructor. Additionally, provide a `CustomApplicationCodeFixProvider` which will inject the required constructor into the syntax tree. Finally, rename the previous `XAD0001` warning to `DNAS0001`. Our convention is as follows: * `DNA`: prefix for .NET for Android messages * `A` for *Analyzers*, `S` for *Suppressors* * 4 digit code. [0]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.diagnostics.diagnosticanalyzer?view=roslyn-dotnet-4.9.0 [1]: https://learn.microsoft.com/en-us/dotnet/api/android.app.application.-ctor?view=net-android-34.0#android-app-application-ctor(system-intptr-android-runtime-jnihandleownership) [2]: https://learn.microsoft.com/en-us/dotnet/api/android.app.application?view=net-android-34.0
- Loading branch information
1 parent
517bc21
commit 715a36a
Showing
10 changed files
with
486 additions
and
88 deletions.
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
src/Microsoft.Android.Sdk.Analysis/AnalyzerReleases.Shipped.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
; Shipped analyzer releases | ||
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md | ||
|
8 changes: 8 additions & 0 deletions
8
src/Microsoft.Android.Sdk.Analysis/AnalyzerReleases.Unshipped.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
; Unshipped analyzer release | ||
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md | ||
|
||
### New Rules | ||
|
||
Rule ID | Category | Severity | Notes | ||
--------|----------|----------|------- | ||
DNAA0001 | Usage | Warning | CustomApplicationAnalyzer |
69 changes: 69 additions & 0 deletions
69
src/Microsoft.Android.Sdk.Analysis/Analyzers/CustomApplicationAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using Microsoft.Android.Sdk.Analysis; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
[DiagnosticAnalyzer (LanguageNames.CSharp)] | ||
public class CustomApplicationAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private const string AndroidApplication = "Android.App.Application"; | ||
public const string DiagnosticId = "DNAA0001"; | ||
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor ( | ||
id: DiagnosticId, | ||
title: Resources.DNAA0001_Title, | ||
messageFormat: Resources.DNAA0001_MessageFormat, | ||
category: "Usage", | ||
defaultSeverity: DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true | ||
); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create (Rule); | ||
|
||
public override void Initialize (AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution (); | ||
|
||
// Register a syntax node action to analyze method declarations | ||
context.RegisterSyntaxNodeAction (AnalyzeClass, SyntaxKind.ClassDeclaration); | ||
} | ||
|
||
private static void AnalyzeClass (SyntaxNodeAnalysisContext context) | ||
{ | ||
var classDeclarationSyntax = context.Node as ClassDeclarationSyntax; | ||
if (classDeclarationSyntax == null) | ||
return; | ||
|
||
var classSymbol = context.SemanticModel.GetDeclaredSymbol (classDeclarationSyntax) as INamedTypeSymbol; | ||
if (classSymbol == null) | ||
return; | ||
|
||
if (!Utilities.IsDerivedFrom (classSymbol, AndroidApplication)) | ||
return; | ||
|
||
var constructors = classDeclarationSyntax.Members | ||
.OfType<ConstructorDeclarationSyntax> (); | ||
|
||
bool foundActivationConstructor = false; | ||
foreach (var constructor in constructors) { | ||
var parameters = constructor.ParameterList.Parameters; | ||
if (parameters.Count != 2) | ||
continue; | ||
if (parameters [0].Type.ToString () != "IntPtr") | ||
continue; | ||
var ns = Utilities.GetNamespaceForParameterType (parameters [1], context.SemanticModel); | ||
var type = parameters [1].Type.ToString(); | ||
var isJniHandle = (ns == "Android.Runtime") && (type == "JniHandleOwnership") || (type == "Android.Runtime.JniHandleOwnership"); | ||
if (!isJniHandle) | ||
continue; | ||
foundActivationConstructor = true; | ||
} | ||
if (!foundActivationConstructor) { | ||
var diagnostic = Diagnostic.Create (Rule, classDeclarationSyntax.Identifier.GetLocation (), classDeclarationSyntax.Identifier.Text); | ||
context.ReportDiagnostic (diagnostic); | ||
} | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
src/Microsoft.Android.Sdk.Analysis/CodeFixes/CustomApplicationCodeFixProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
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; | ||
|
||
[ExportCodeFixProvider (LanguageNames.CSharp, Name = nameof (CustomApplicationCodeFixProvider)), Shared] | ||
public class CustomApplicationCodeFixProvider : CodeFixProvider | ||
{ | ||
private const string title = "Fix Activation Constructor"; | ||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create (CustomApplicationAnalyzer.DiagnosticId); | ||
|
||
public sealed override FixAllProvider GetFixAllProvider () | ||
{ | ||
return WellKnownFixAllProviders.BatchFixer; | ||
} | ||
|
||
public 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; | ||
var classDeclaration = root.FindToken (diagnosticSpan.Start).Parent.AncestorsAndSelf () | ||
.OfType<ClassDeclarationSyntax> ().First (); | ||
context.RegisterCodeFix (CodeAction.Create (title, c => | ||
InjectConstructorAsync (context.Document, classDeclaration, c), equivalenceKey: title), diagnostic); | ||
} | ||
|
||
private async Task<Document> InjectConstructorAsync (Document document, ClassDeclarationSyntax classDeclaration, CancellationToken cancellationToken) | ||
{ | ||
var constructor = CreateConstructorWithParameters (classDeclaration.Identifier); | ||
var newClassDeclaration = classDeclaration.AddMembers (constructor); | ||
var root = await document.GetSyntaxRootAsync (cancellationToken); | ||
var newRoot = root.ReplaceNode (classDeclaration, newClassDeclaration); | ||
return document.WithSyntaxRoot (newRoot); | ||
} | ||
|
||
private ConstructorDeclarationSyntax CreateConstructorWithParameters (SyntaxToken identifier) | ||
{ | ||
var parameters = SyntaxFactory.ParameterList (SyntaxFactory.SeparatedList (new [] { | ||
SyntaxFactory.Parameter (SyntaxFactory.Identifier ("javaReference")) | ||
.WithType (SyntaxFactory.ParseTypeName ("IntPtr")), | ||
SyntaxFactory.Parameter (SyntaxFactory.Identifier ("transfer")) | ||
.WithType (SyntaxFactory.ParseTypeName ("Android.Runtime.JniHandleOwnership")) | ||
})); | ||
var baseArguments = SyntaxFactory.ArgumentList (SyntaxFactory.SeparatedList (new [] { | ||
SyntaxFactory.Argument (SyntaxFactory.IdentifierName ("javaReference")), | ||
SyntaxFactory.Argument (SyntaxFactory.IdentifierName ("transfer")) | ||
})); | ||
var constructorInitializer = SyntaxFactory.ConstructorInitializer (SyntaxKind.BaseConstructorInitializer, baseArguments); | ||
var body = SyntaxFactory.Block (); | ||
var constructor = SyntaxFactory.ConstructorDeclaration (identifier) | ||
.WithModifiers (SyntaxFactory.TokenList (SyntaxFactory.Token (SyntaxKind.PublicKeyword))) | ||
.WithParameterList (parameters) | ||
.WithInitializer (constructorInitializer) | ||
.WithBody (body); | ||
return constructor; | ||
} | ||
} |
52 changes: 35 additions & 17 deletions
52
src/Microsoft.Android.Sdk.Analysis/Microsoft.Android.Sdk.Analysis.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,37 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<Import Project="..\..\Configuration.props" /> | ||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> | ||
<OutputPath>$(MicrosoftAndroidSdkAnalysisOutDir)</OutputPath> | ||
<IsRoslynComponent>true</IsRoslynComponent> | ||
<LangVersion>latest</LangVersion> | ||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> | ||
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> | ||
</ItemGroup> | ||
<Import Project="..\..\Configuration.props" /> | ||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> | ||
<OutputPath>$(MicrosoftAndroidSdkAnalysisOutDir)</OutputPath> | ||
<IsRoslynComponent>true</IsRoslynComponent> | ||
<LangVersion>latest</LangVersion> | ||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> | ||
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.11.0" PrivateAssets="all" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Compile Update="Properties\Resources.Designer.cs"> | ||
<DesignTime>True</DesignTime> | ||
<AutoGen>True</AutoGen> | ||
<DependentUpon>Resources.resx</DependentUpon> | ||
</Compile> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<EmbeddedResource Update="Properties\Resources.resx"> | ||
<Generator>PublicResXFileCodeGenerator</Generator> | ||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | ||
</EmbeddedResource> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" /> | ||
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> | ||
</ItemGroup> | ||
</Project> |
85 changes: 85 additions & 0 deletions
85
src/Microsoft.Android.Sdk.Analysis/Properties/Resources.Designer.cs
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.