CodeWriter.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. using System;
  2. using System.CodeDom.Compiler;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.IO;
  5. using System.Text;
  6. using Microsoft.CodeAnalysis.Text;
  7. using Terminal.Gui.Analyzers.Internal.Constants;
  8. namespace Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
  9. /// <summary>
  10. /// The class responsible for turning an <see cref="EnumExtensionMethodsGenerationInfo"/>
  11. /// into actual C# code.
  12. /// </summary>
  13. /// <remarks>Try to use this type as infrequently as possible.</remarks>
  14. /// <param name="metadata">
  15. /// A reference to an <see cref="IGeneratedTypeMetadata{TSelf}"/> which will be used
  16. /// to generate the extension class code. The object will not be validated,
  17. /// so it is critical that it be correct and remain unchanged while in use
  18. /// by an instance of this class. Behavior if those rules are not followed
  19. /// is undefined.
  20. /// </param>
  21. [SuppressMessage ("CodeQuality", "IDE0079", Justification = "Suppressions here are intentional and the warnings they disable are just noise.")]
  22. internal sealed class CodeWriter (in EnumExtensionMethodsGenerationInfo metadata) : IStandardCSharpCodeGenerator<EnumExtensionMethodsGenerationInfo>
  23. {
  24. // Using the null suppression operator here because this will always be
  25. // initialized to non-null before a reference to it is returned.
  26. private SourceText _sourceText = null!;
  27. /// <inheritdoc/>
  28. public EnumExtensionMethodsGenerationInfo Metadata
  29. {
  30. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  31. [return: NotNull]
  32. get;
  33. [param: DisallowNull]
  34. set;
  35. } = metadata;
  36. /// <inheritdoc/>
  37. public ref readonly SourceText GenerateSourceText (Encoding? encoding = null)
  38. {
  39. encoding ??= Encoding.UTF8;
  40. _sourceText = SourceText.From (GetFullSourceText (), encoding);
  41. return ref _sourceText;
  42. }
  43. /// <summary>
  44. /// Gets the using directive for the namespace containing the enum,
  45. /// if different from the extension class namespace, or an empty string, if they are the same.
  46. /// </summary>
  47. private string EnumNamespaceUsingDirective => Metadata.TargetTypeNamespace != Metadata.GeneratedTypeNamespace
  48. // ReSharper disable once HeapView.ObjectAllocation
  49. ? $"using {Metadata.TargetTypeNamespace};"
  50. : string.Empty;
  51. private string EnumTypeKeyword => Metadata.EnumBackingTypeCode switch
  52. {
  53. TypeCode.Int32 => "int",
  54. TypeCode.UInt32 => "uint",
  55. _ => string.Empty
  56. };
  57. /// <summary>Gets the class declaration line.</summary>
  58. private string ExtensionClassDeclarationLine => $"public static partial class {Metadata.GeneratedTypeName}";
  59. // ReSharper disable once HeapView.ObjectAllocation
  60. /// <summary>Gets the XmlDoc for the extension class declaration.</summary>
  61. private string ExtensionClassDeclarationXmlDoc =>
  62. $"/// <summary>Extension methods for the <see cref=\"{Metadata.TargetTypeFullName}\"/> <see langword=\"enum\" /> type.</summary>";
  63. // ReSharper disable once HeapView.ObjectAllocation
  64. /// <summary>Gets the extension class file-scoped namespace directive.</summary>
  65. private string ExtensionClassNamespaceDirective => $"namespace {Metadata.GeneratedTypeNamespace};";
  66. /// <summary>
  67. /// An attribute to decorate the extension class with for easy mapping back to the target enum type, for reflection and
  68. /// analysis.
  69. /// </summary>
  70. private string ExtensionsForTypeAttributeLine => $"[ExtensionsForEnumType<{Metadata.TargetTypeFullName}>]";
  71. /// <summary>
  72. /// Creates the code for the FastHasFlags method.
  73. /// </summary>
  74. /// <remarks>
  75. /// Since the generator already only writes code for enums backed by <see langword="int"/> and <see langword="uint"/>,
  76. /// this method is safe, as we'll always be using a DWORD.
  77. /// </remarks>
  78. /// <param name="w">An instance of an <see cref="IndentedTextWriter"/> to write to.</param>
  79. private void GetFastHasFlagsMethods (IndentedTextWriter w)
  80. {
  81. // The version taking the same enum type as the check value.
  82. w.WriteLine (
  83. $"/// <summary>Determines if the specified flags are set in the current value of this <see cref=\"{Metadata.TargetTypeFullName}\" />.</summary>");
  84. w.WriteLine ("/// <remarks>NO VALIDATION IS PERFORMED!</remarks>");
  85. w.WriteLine (
  86. $"/// <returns>True, if all flags present in <paramref name=\"checkFlags\" /> are also present in the current value of the <see cref=\"{Metadata.TargetTypeFullName}\" />.<br />Otherwise false.</returns>");
  87. w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining);
  88. w.Push (
  89. $"{Metadata.Accessibility.ToCSharpString ()} static bool FastHasFlags (this {Metadata.TargetTypeFullName} e, {Metadata.TargetTypeFullName} checkFlags)");
  90. w.WriteLine ($"ref uint enumCurrentValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref e);");
  91. w.WriteLine ($"ref uint checkFlagsValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref checkFlags);");
  92. w.WriteLine ("return (enumCurrentValueRef & checkFlagsValueRef) == checkFlagsValueRef;");
  93. w.Pop ();
  94. // The version taking the underlying type of the enum as the check value.
  95. w.WriteLine (
  96. $"/// <summary>Determines if the specified mask bits are set in the current value of this <see cref=\"{Metadata.TargetTypeFullName}\" />.</summary>");
  97. w.WriteLine (
  98. $"/// <param name=\"e\">The <see cref=\"{Metadata.TargetTypeFullName}\" /> value to check against the <paramref name=\"mask\" /> value.</param>");
  99. w.WriteLine ("/// <param name=\"mask\">A mask to apply to the current value.</param>");
  100. w.WriteLine (
  101. $"/// <returns>True, if all bits set to 1 in the mask are also set to 1 in the current value of the <see cref=\"{Metadata.TargetTypeFullName}\" />.<br />Otherwise false.</returns>");
  102. w.WriteLine ("/// <remarks>NO VALIDATION IS PERFORMED!</remarks>");
  103. w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining);
  104. w.Push (
  105. $"{Metadata.Accessibility.ToCSharpString ()} static bool FastHasFlags (this {Metadata.TargetTypeFullName} e, {EnumTypeKeyword} mask)");
  106. w.WriteLine ($"ref {EnumTypeKeyword} enumCurrentValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},{EnumTypeKeyword}> (ref e);");
  107. w.WriteLine ("return (enumCurrentValueRef & mask) == mask;");
  108. w.Pop ();
  109. }
  110. /// <summary>
  111. /// Creates the code for the FastIsDefined method.
  112. /// </summary>
  113. [SuppressMessage ("ReSharper", "SwitchStatementHandlesSomeKnownEnumValuesWithDefault", Justification = "Only need to handle int and uint.")]
  114. [SuppressMessage ("ReSharper", "SwitchStatementMissingSomeEnumCasesNoDefault", Justification = "Only need to handle int and uint.")]
  115. private void GetFastIsDefinedMethod (IndentedTextWriter w)
  116. {
  117. w.WriteLine (
  118. $"/// <summary>Determines if the specified <see langword=\"{EnumTypeKeyword}\" /> value is explicitly defined as a named value of the <see cref=\"{Metadata.TargetTypeFullName}\" /> <see langword=\"enum\" /> type.</summary>");
  119. w.WriteLine (
  120. "/// <remarks>Only explicitly named values return true, as with IsDefined. Combined valid flag values of flags enums which are not explicitly named will return false.</remarks>");
  121. w.Push (
  122. $"{Metadata.Accessibility.ToCSharpString ()} static bool FastIsDefined (this {Metadata.TargetTypeFullName} e, {EnumTypeKeyword} value)");
  123. w.Push ("return value switch");
  124. switch (Metadata.EnumBackingTypeCode)
  125. {
  126. case TypeCode.Int32:
  127. foreach (int definedValue in Metadata._intMembers)
  128. {
  129. w.WriteLine ($"{definedValue:D} => true,");
  130. }
  131. break;
  132. case TypeCode.UInt32:
  133. foreach (uint definedValue in Metadata._uIntMembers)
  134. {
  135. w.WriteLine ($"{definedValue:D} => true,");
  136. }
  137. break;
  138. }
  139. w.WriteLine ("_ => false");
  140. w.Pop ("};");
  141. w.Pop ();
  142. }
  143. private string GetFullSourceText ()
  144. {
  145. StringBuilder sb = new (
  146. $"""
  147. {Strings.Templates.StandardHeader}
  148. [assembly: {Strings.AssemblyExtendedEnumTypeAttributeFullName} (typeof({Metadata.TargetTypeFullName}), typeof({Metadata.GeneratedTypeFullName}))]
  149. {EnumNamespaceUsingDirective}
  150. {ExtensionClassNamespaceDirective}
  151. {ExtensionClassDeclarationXmlDoc}
  152. {Strings.Templates.AttributesForGeneratedTypes}
  153. {ExtensionsForTypeAttributeLine}
  154. {ExtensionClassDeclarationLine}
  155. """,
  156. 4096);
  157. using IndentedTextWriter w = new (new StringWriter (sb));
  158. w.Push ();
  159. GetNamedValuesToInt32Method (w);
  160. GetNamedValuesToUInt32Method (w);
  161. if (Metadata.GenerateFastIsDefined)
  162. {
  163. GetFastIsDefinedMethod (w);
  164. }
  165. if (Metadata.GenerateFastHasFlags)
  166. {
  167. GetFastHasFlagsMethods (w);
  168. }
  169. w.Pop ();
  170. w.Flush ();
  171. return sb.ToString ();
  172. }
  173. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  174. private void GetNamedValuesToInt32Method (IndentedTextWriter w)
  175. {
  176. w.WriteLine (
  177. $"/// <summary>Directly converts this <see cref=\"{Metadata.TargetTypeFullName}\" /> value to an <see langword=\"int\" /> value with the same binary representation.</summary>");
  178. w.WriteLine ("/// <remarks>NO VALIDATION IS PERFORMED!</remarks>");
  179. w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining);
  180. w.Push ($"{Metadata.Accessibility.ToCSharpString ()} static int AsInt32 (this {Metadata.TargetTypeFullName} e)");
  181. w.WriteLine ($"return Unsafe.As<{Metadata.TargetTypeFullName},int> (ref e);");
  182. w.Pop ();
  183. }
  184. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  185. private void GetNamedValuesToUInt32Method (IndentedTextWriter w)
  186. {
  187. w.WriteLine (
  188. $"/// <summary>Directly converts this <see cref=\"{Metadata.TargetTypeFullName}\" /> value to a <see langword=\"uint\" /> value with the same binary representation.</summary>");
  189. w.WriteLine ("/// <remarks>NO VALIDATION IS PERFORMED!</remarks>");
  190. w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining);
  191. w.Push ($"{Metadata.Accessibility.ToCSharpString ()} static uint AsUInt32 (this {Metadata.TargetTypeFullName} e)");
  192. w.WriteLine ($"return Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref e);");
  193. w.Pop ();
  194. }
  195. }