GenerateEnumExtensionMethodsAttributeAnalyzer.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. #define JETBRAINS_ANNOTATIONS
  2. using System.Collections.Immutable;
  3. using System.Linq;
  4. using JetBrains.Annotations;
  5. using Microsoft.CodeAnalysis;
  6. using Microsoft.CodeAnalysis.CSharp;
  7. using Microsoft.CodeAnalysis.Diagnostics;
  8. using Terminal.Gui.Analyzers.Internal.Attributes;
  9. using Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
  10. namespace Terminal.Gui.Analyzers.Internal.Analyzers;
  11. /// <summary>
  12. /// Design-time analyzer that checks for proper use of <see cref="GenerateEnumExtensionMethodsAttribute"/>.
  13. /// </summary>
  14. [DiagnosticAnalyzer (LanguageNames.CSharp)]
  15. [UsedImplicitly]
  16. internal sealed class GenerateEnumExtensionMethodsAttributeAnalyzer : DiagnosticAnalyzer
  17. {
  18. // ReSharper disable once InconsistentNaming
  19. private static readonly DiagnosticDescriptor TG0001_GlobalNamespaceNotSupported = new (
  20. // ReSharper restore InconsistentNaming
  21. "TG0001",
  22. $"{nameof (GenerateEnumExtensionMethodsAttribute)} not supported on global enums",
  23. "{0} is in the global namespace, which is not supported by the source generator ({1}) used by {2}. Move the enum to a namespace or remove the attribute.",
  24. "Usage",
  25. DiagnosticSeverity.Error,
  26. true,
  27. null,
  28. null,
  29. WellKnownDiagnosticTags.NotConfigurable,
  30. WellKnownDiagnosticTags.Compiler);
  31. // ReSharper disable once InconsistentNaming
  32. private static readonly DiagnosticDescriptor TG0002_UnderlyingTypeNotSupported = new (
  33. "TG0002",
  34. $"{nameof (GenerateEnumExtensionMethodsAttribute)} not supported for this enum type",
  35. "{0} has an underlying type of {1}, which is not supported by the source generator ({2}) used by {3}. Only enums backed by int or uint are supported.",
  36. "Usage",
  37. DiagnosticSeverity.Error,
  38. true,
  39. null,
  40. null,
  41. WellKnownDiagnosticTags.NotConfigurable,
  42. WellKnownDiagnosticTags.Compiler);
  43. /// <inheritdoc/>
  44. public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
  45. [
  46. TG0001_GlobalNamespaceNotSupported,
  47. TG0002_UnderlyingTypeNotSupported
  48. ];
  49. /// <inheritdoc/>
  50. public override void Initialize (AnalysisContext context)
  51. {
  52. context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.None);
  53. context.EnableConcurrentExecution ();
  54. context.RegisterSyntaxNodeAction (CheckAttributeLocations, SyntaxKind.EnumDeclaration);
  55. return;
  56. static void CheckAttributeLocations (SyntaxNodeAnalysisContext analysisContext)
  57. {
  58. ISymbol? symbol = analysisContext.SemanticModel.GetDeclaredSymbol (analysisContext.Node) as INamedTypeSymbol;
  59. if (symbol is not INamedTypeSymbol { EnumUnderlyingType: { } } enumSymbol)
  60. {
  61. // Somehow not even an enum declaration.
  62. // Skip it.
  63. return;
  64. }
  65. // Check attributes for those we care about and react accordingly.
  66. foreach (AttributeData attributeData in enumSymbol.GetAttributes ())
  67. {
  68. if (attributeData.AttributeClass?.Name != nameof (GenerateEnumExtensionMethodsAttribute))
  69. {
  70. // Just skip - not an interesting attribute.
  71. continue;
  72. }
  73. // Check enum underlying type for supported types (int and uint, currently)
  74. // Report TG0002 if unsupported underlying type.
  75. if (enumSymbol.EnumUnderlyingType is not { SpecialType: SpecialType.System_Int32 or SpecialType.System_UInt32 })
  76. {
  77. analysisContext.ReportDiagnostic (
  78. Diagnostic.Create (
  79. TG0002_UnderlyingTypeNotSupported,
  80. enumSymbol.Locations.FirstOrDefault (),
  81. enumSymbol.Name,
  82. enumSymbol.EnumUnderlyingType.Name,
  83. nameof (EnumExtensionMethodsIncrementalGenerator),
  84. nameof (GenerateEnumExtensionMethodsAttribute)
  85. )
  86. );
  87. }
  88. // Check enum namespace (only non-global supported, currently)
  89. // Report TG0001 if in the global namespace.
  90. if (enumSymbol.ContainingSymbol is not INamespaceSymbol { IsGlobalNamespace: false })
  91. {
  92. analysisContext.ReportDiagnostic (
  93. Diagnostic.Create (
  94. TG0001_GlobalNamespaceNotSupported,
  95. enumSymbol.Locations.FirstOrDefault (),
  96. enumSymbol.Name,
  97. nameof (EnumExtensionMethodsIncrementalGenerator),
  98. nameof (GenerateEnumExtensionMethodsAttribute)
  99. )
  100. );
  101. }
  102. }
  103. }
  104. }
  105. }