Skip to content

Commit

Permalink
Add DiagnosticSuppressor to suppress the IDE0002 Style Hint in the …
Browse files Browse the repository at this point in the history
…IDE (#9310)

Context: #8381

Add a `DiagnosticSuppressor` to "turn off" the `IDE0002` style
diagnostic message which incorrectly tells users to use the
`_Microsoft.Android.Resource.Designer.ResourceConstant` type directly.
This can cause allot of annoyance with our users because it appears on
EVERY single usage of `Resource.*`. So you end up with what looks like
code spagetti. 

So we need to start shipping an `Analyzer` assembly along with the
`Ref` framework pack. This is the place these things need to go.
Unfortunately it means that the older frameworks will not get this
analyzer. Only the current one. 

On the packaging side, the Analyzer assembly has to go in a
`analyzers/dotnet/<language>` folder in the .Ref Nuget Package. There
also needs to be an entry in the `FrameworkList.xml` file which has a
`Type="Analyzer" ` and a `Language="cs"`. This allows the IDE's to
pickup the code. We can ship both regular Analyzers and the
DiagnosticSuppressors in the same assembly. So we can extend this with
more if needed.

How this works is we use Rosyln to look for the IDE0002 diagnsotic
message, we then check the code and see if it is refering to a
`_Microsoft.Android.Resource.Designer.*` derived class. If it is , we
add a suppression. This will stop the hint appearing in the IDE. I
have tested this on VS in devbox and it appears to work . 

Also we generate a Resource Designer assembly and an Intermediate
source file at build time. Both of these contain classes which should
have the `GeneratedCode` Attribute. So lets add it. The version will
be the same as the build assembly used to generate it.
  • Loading branch information
dellis1972 authored Sep 30, 2024
1 parent 27b5d2e commit b3079db
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 6 deletions.
1 change: 1 addition & 0 deletions Configuration.props
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
<MicrosoftAndroidx64PackDir>$(BuildOutputDirectory)lib\packs\Microsoft.Android.Runtime.$(AndroidApiLevel).android-x64\$(AndroidPackVersion)\runtimes\android-x64\</MicrosoftAndroidx64PackDir>
<MicrosoftAndroidSdkPackDir>$(BuildOutputDirectory)lib\packs\$(MicrosoftAndroidSdkPackName)\$(AndroidPackVersion)\</MicrosoftAndroidSdkPackDir>
<MicrosoftAndroidSdkOutDir>$(MicrosoftAndroidSdkPackDir)\tools\</MicrosoftAndroidSdkOutDir>
<MicrosoftAndroidSdkAnalysisOutDir>$(BuildOutputDirectory)lib\packs\Microsoft.Android.Ref.$(AndroidApiLevel)\$(AndroidPackVersion)\analyzers\dotnet\cs\</MicrosoftAndroidSdkAnalysisOutDir>
<MakeConcurrency Condition=" '$(MakeConcurrency)' == '' And '$(HostCpuCount)' != '' ">-j$(HostCpuCount)</MakeConcurrency>
<ManagedRuntime Condition=" '$(ManagedRuntime)' == '' And '$(OS)' != 'Windows_NT' ">mono</ManagedRuntime>
<ManagedRuntimeArgs Condition=" '$(ManagedRuntimeArgs)' == '' And '$(ManagedRuntime)' == 'mono' ">--debug=casts</ManagedRuntimeArgs>
Expand Down
9 changes: 9 additions & 0 deletions Xamarin.Android.Build.Tasks.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Android.Build.Bas
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk", "external\xamarin-android-tools\src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4A5EE838-A906-4711-972E-E680B0AA68BD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Sdk.Analysis", "src\Microsoft.Android.Sdk.Analysis\Microsoft.Android.Sdk.Analysis.csproj", "{0D00DD34-3E94-4166-9DEE-12355E4C98A0}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems*{3f1f2f50-af1a-4a5a-bedb-193372f068d7}*SharedItemsImports = 5
Expand Down Expand Up @@ -74,6 +78,10 @@ Global
{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.Build.0 = Release|Any CPU
{0D00DD34-3E94-4166-9DEE-12355E4C98A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D00DD34-3E94-4166-9DEE-12355E4C98A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D00DD34-3E94-4166-9DEE-12355E4C98A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D00DD34-3E94-4166-9DEE-12355E4C98A0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -84,6 +92,7 @@ Global
{DE40756E-57F6-4AF2-B155-55E3A88CCED8} = {385E71CC-BAE5-488B-805E-ACAE55F01DF5}
{3DE17662-DCD6-4F49-AF06-D39AACC8649A} = {385E71CC-BAE5-488B-805E-ACAE55F01DF5}
{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157} = {385E71CC-BAE5-488B-805E-ACAE55F01DF5}
{0D00DD34-3E94-4166-9DEE-12355E4C98A0} = {4A5EE838-A906-4711-972E-E680B0AA68BD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F32556C5-6FD4-4F1D-884A-DEDF2EE865F6}
Expand Down
9 changes: 9 additions & 0 deletions Xamarin.Android.sln
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "create-android-api", "build
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.Aidl-Tests", "tests\Xamarin.Android.Tools.Aidl-Tests\Xamarin.Android.Tools.Aidl-Tests.csproj", "{A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FFCF518F-2A4A-40A2-9174-2EE13B76C723}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Sdk.Analysis", "src\Microsoft.Android.Sdk.Analysis\Microsoft.Android.Sdk.Analysis.csproj", "{5E806C9F-1B67-4B6B-A6AB-258834250DBB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|AnyCPU = Debug|AnyCPU
Expand Down Expand Up @@ -335,6 +339,10 @@ Global
{A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|AnyCPU.ActiveCfg = Release|Any CPU
{A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|AnyCPU.Build.0 = Release|Any CPU
{5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU
{5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|AnyCPU.ActiveCfg = Release|Any CPU
{5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|AnyCPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -393,6 +401,7 @@ Global
{C0E44558-FEE3-4DD3-986A-3F46DD1BF41B} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
{BA4D889D-066B-4C2C-A973-09E319CBC396} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62}
{A39B6D7C-6616-40D6-8AE4-C6CEE93D2708} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483}
{5E806C9F-1B67-4B6B-A6AB-258834250DBB} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {53A1F287-EFB2-4D97-A4BB-4A5E145613F6}
Expand Down
1 change: 1 addition & 0 deletions build-tools/create-packs/ConfigureLocalWorkload.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<ItemGroup>
<_FrameworkListInputs Include="$(MicrosoftAndroidRefPackDir)**" />
<_FrameworkListInputs Include="$(MicrosoftAndroidSdkAnalysisOutDir)Microsoft.Android.Sdk.Analysis.dll" />
<_FrameworkListOutputs Include="$(BuildOutputDirectory)lib\packs\Microsoft.Android.Ref.$(AndroidDefaultTargetDotnetApiLevel)\$(AndroidPackVersion)\data\FrameworkList.xml" />
<_FrameworkListOutputs Include="$(BuildOutputDirectory)lib\packs\Microsoft.Android.Ref.$(AndroidLatestStableApiLevel)\$(AndroidPackVersion)\data\FrameworkList.xml" />
<_FrameworkListOutputs Include="$(BuildOutputDirectory)lib\packs\Microsoft.Android.Ref.$(AndroidLatestUnstableApiLevel)\$(AndroidPackVersion)\data\FrameworkList.xml" />
Expand Down
2 changes: 1 addition & 1 deletion build-tools/create-packs/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
Files="@(_PackageFiles)"
FileClassifications="@(FrameworkListFileClass)"
TargetFile="$(FrameworkListFile)"
TargetFilePrefixes="ref;runtimes"
TargetFilePrefixes="ref;runtimes;analyzers"
RootAttributes="@(FrameworkListRootAttributes)"
/>
<ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions build-tools/create-packs/Microsoft.Android.Ref.proj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ by projects that use the Microsoft.Android framework in .NET 6+.
<PackageId>Microsoft.Android.Ref.$(AndroidApiLevel)</PackageId>
<Description>Microsoft.Android reference assemblies for API $(AndroidApiLevel). Please do not reference directly.</Description>
<_AndroidRefPackAssemblyPath>ref\$(DotNetTargetFramework)</_AndroidRefPackAssemblyPath>
<_AndroidRefPackAnalyzersPath>analyzers\dotnet\cs</_AndroidRefPackAnalyzersPath>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -36,11 +37,14 @@ by projects that use the Microsoft.Android framework in .NET 6+.
<_AndroidRefPackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)ref\Mono.Android.Runtime.dll" />
<!-- Always include stable Mono.Android.Export.dll -->
<_AndroidRefPackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\ref\Mono.Android.Export.dll" />
<_AndroidRefPackAnalyzers Include="$(MicrosoftAndroidSdkAnalysisOutDir)Microsoft.Android.Sdk.Analysis.dll" />
<FrameworkListFileClass Include="@(_AndroidRefPackAssemblies->'%(Filename)%(Extension)')" Profile="Android" />
<FrameworkListFileClass Include="@(_AndroidRefPackAnalyzers->'%(Filename)%(Extension)')" Profile="Android" />
</ItemGroup>

<ItemGroup>
<_PackageFiles Include="@(_AndroidRefPackAssemblies)" PackagePath="$(_AndroidRefPackAssemblyPath)" TargetPath="$(_AndroidRefPackAssemblyPath)" />
<_PackageFiles Include="@(_AndroidRefPackAnalyzers)" PackagePath="$(_AndroidRefPackAnalyzersPath)" TargetPath="$(_AndroidRefPackAnalyzersPath)" />
<_PackageFiles Include="$(_MonoAndroidNETDefaultOutDir)Java.Interop.xml" PackagePath="$(_AndroidRefPackAssemblyPath)" />
<_PackageFiles Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.xml" PackagePath="$(_AndroidRefPackAssemblyPath)" />
<_PackageFiles Include="$(_MonoAndroidNETDefaultOutDir)mono.android.jar" PackagePath="$(_AndroidRefPackAssemblyPath)" />
Expand Down
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<MicrosoftNETILLinkTasksPackageVersion>9.0.0-rtm.24473.2</MicrosoftNETILLinkTasksPackageVersion>
<MicrosoftNETCoreAppRefPackageVersion>9.0.0-rtm.24473.2</MicrosoftNETCoreAppRefPackageVersion>
<MicrosoftDotNetApiCompatPackageVersion>7.0.0-beta.22103.1</MicrosoftDotNetApiCompatPackageVersion>
<MicrosoftDotNetBuildTasksFeedPackageVersion>9.0.0-beta.24408.2</MicrosoftDotNetBuildTasksFeedPackageVersion>
<MicrosoftDotNetBuildTasksFeedPackageVersion>10.0.0-beta.24476.2</MicrosoftDotNetBuildTasksFeedPackageVersion>
<MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion>9.0.0-rtm.24469.1</MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion>
<MicrosoftNETWorkloadEmscriptenPackageVersion>$(MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion)</MicrosoftNETWorkloadEmscriptenPackageVersion>
<MicrosoftTemplateEngineTasksPackageVersion>7.0.100-rc.1.22410.7</MicrosoftTemplateEngineTasksPackageVersion>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<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>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Linq;
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ResourceDesignerDiagnosticSuppressor : DiagnosticSuppressor
{
private const string DesignerNamespace = "_Microsoft.Android.Resource.Designer";
private static readonly SuppressionDescriptor Rule = new(
"XAD0001",
"IDE0002",
"The Resource Designer class should not be simplified."
);

public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions
=> ImmutableArray.Create(Rule);

public override void ReportSuppressions(SuppressionAnalysisContext context)
{
foreach (var diagnostic in context.ReportedDiagnostics)
{
if (diagnostic.Id != Rule.SuppressedDiagnosticId)
continue;
Location location = diagnostic.Location;
SyntaxTree syntaxTree = location.SourceTree;
if (syntaxTree is null)
continue;

SyntaxNode root = syntaxTree.GetRoot(context.CancellationToken);
SyntaxNode syntaxNode = root.FindNode(location.SourceSpan)
.DescendantNodesAndSelf()
.FirstOrDefault ();

if (syntaxNode is null)
continue;

SemanticModel model = context.GetSemanticModel(syntaxTree);
ISymbol typeSymbol = model.GetSymbolInfo (syntaxNode).Symbol;
if (typeSymbol is not INamedTypeSymbol namedTypeSymbol)
continue;

if (IsResourceDesignerDerivedType(namedTypeSymbol))
{
Suppression suppression = Suppression.Create(Rule, diagnostic);
context.ReportSuppression(suppression);
}
}
}

private static bool IsResourceDesignerDerivedType(INamedTypeSymbol typeSymbol)
{
return IsDerivedFrom(typeSymbol, DesignerNamespace);
}

private static bool IsDerivedFrom(INamedTypeSymbol typeSymbol, string baseClassName)
{
while (typeSymbol != null)
{
if (typeSymbol.ToDisplayString().StartsWith(baseClassName))
{
return true;
}
typeSymbol = typeSymbol.BaseType;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ bool Run (DirectoryAssemblyResolver res)
TypeReference e = ImportType ("System.ComponentModel.EditorBrowsableState", module, netstandardDef.MainModule);
var editorBrowserAttr = new CustomAttribute (editorBrowserConstructor);
editorBrowserAttr.ConstructorArguments.Add (new CustomAttributeArgument (e, System.ComponentModel.EditorBrowsableState.Never));

MethodReference generatedCodeConstructor = ImportCustomAttributeConstructor (cache, "System.CodeDom.Compiler.GeneratedCodeAttribute", module, netstandardDef.MainModule, argCount: 2);
var generatedCodeAttr = new CustomAttribute (generatedCodeConstructor);
generatedCodeAttr.ConstructorArguments.Add (new CustomAttributeArgument (module.TypeSystem.String, nameof(GenerateResourceDesignerAssembly)));
var version = typeof(GenerateResourceDesignerAssembly).Assembly.GetName().Version;
generatedCodeAttr.ConstructorArguments.Add (new CustomAttributeArgument (module.TypeSystem.String, version.ToString ()));

var att = TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.BeforeFieldInit;

Expand All @@ -139,6 +145,7 @@ bool Run (DirectoryAssemblyResolver res)
);
CreateCtor (cache, resourceDesigner, module);
resourceDesigner.CustomAttributes.Add (editorBrowserAttr);
resourceDesigner.CustomAttributes.Add (generatedCodeAttr);
module.Types.Add (resourceDesigner);
TypeDefinition constDesigner = null;
if (IsApplication) {
Expand All @@ -152,6 +159,7 @@ bool Run (DirectoryAssemblyResolver res)
);
CreateCtor (cache, constDesigner, module);
constDesigner.CustomAttributes.Add (editorBrowserAttr);
constDesigner.CustomAttributes.Add (generatedCodeAttr);
module.Types.Add (constDesigner);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ public class GenerateResourceDesignerIntermediateClass : AndroidTask
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.CodeDom.Compiler;
namespace %NAMESPACE% {
#pragma warning disable IDE0002
/// <summary>
/// Android Resource Designer class.
/// Exposes the Android Resource designer assembly into the project Namespace.
/// </summary>
[GeneratedCode(""%TOOL%"", ""%VERSION%"")]
public partial class Resource : %BASECLASS% {
}
#pragma warning restore IDE0002
Expand All @@ -40,6 +42,7 @@ public partial class Resource : %BASECLASS% {
//------------------------------------------------------------------------------
namespace %NAMESPACE%
[<type:System.CodeDom.Compiler.GeneratedCode(""%TOOL%"", ""%VERSION%"")>]
type Resource = %BASECLASS%
";

Expand All @@ -54,11 +57,19 @@ public override bool RunTask ()
//bool isVB = string.Equals (extension, ".vb", StringComparison.OrdinalIgnoreCase);
bool isFSharp = string.Equals (language, "F#", StringComparison.OrdinalIgnoreCase);
bool isCSharp = string.Equals (language, "C#", StringComparison.OrdinalIgnoreCase);
var version = typeof(GenerateResourceDesignerIntermediateClass).Assembly.GetName().Version;
string template = "";
if (isCSharp)
template = CSharpTemplate.Replace ("%NAMESPACE%", Namespace).Replace ("%BASECLASS%", ns);
else if (isFSharp)
template = FSharpTemplate.Replace ("%NAMESPACE%", Namespace).Replace ("%BASECLASS%", ns);
if (isCSharp) {
template = CSharpTemplate.Replace ("%NAMESPACE%", Namespace)
.Replace ("%BASECLASS%", ns)
.Replace ("%VERSION%", version.ToString ())
.Replace ("%TOOL%", nameof (GenerateResourceDesignerIntermediateClass));
} else if (isFSharp) {
template = FSharpTemplate.Replace ("%NAMESPACE%", Namespace)
.Replace ("%BASECLASS%", ns)
.Replace ("%VERSION%", version.ToString ())
.Replace ("%TOOL%", nameof (GenerateResourceDesignerIntermediateClass));
}

Files.CopyIfStringChanged (template, OutputFile.ItemSpec);
return !Log.HasLoggedErrors;
Expand Down

0 comments on commit b3079db

Please sign in to comment.