Merge branch 'v2_develop' into v2_textview-textfield-cursor-fix_3431

This commit is contained in:
Tig
2024-04-30 13:57:37 -06:00
committed by GitHub
65 changed files with 4984 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Debugging;
class Program
{
static void Main (string [] args)
{
}
}
[GenerateEnumExtensionMethods]
public enum TestEnum
{
Zero = 0,
One,
Two = 2,
Three,
Six = 6
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.9.2" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Terminal.Gui.Analyzers.Internal\Terminal.Gui.Analyzers.Internal.csproj">
<PrivateAssets>all</PrivateAssets>
<OutputItemType>Analyzer</OutputItemType>
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
internal class SignedEnumMemberValues
{
internal const int Bit31 = ~0b_01111111_11111111_11111111_11111111;
internal const int Bit30 = 0b_01000000_00000000_00000000_00000000;
internal const int Bit29 = 0b_00100000_00000000_00000000_00000000;
internal const int Bit28 = 0b_00010000_00000000_00000000_00000000;
internal const int Bit27 = 0b_00001000_00000000_00000000_00000000;
internal const int Bit26 = 0b_00000100_00000000_00000000_00000000;
internal const int Bit25 = 0b_00000010_00000000_00000000_00000000;
internal const int Bit24 = 0b_00000001_00000000_00000000_00000000;
internal const int Bit23 = 0b_00000000_10000000_00000000_00000000;
internal const int Bit22 = 0b_00000000_01000000_00000000_00000000;
internal const int Bit21 = 0b_00000000_00100000_00000000_00000000;
internal const int Bit20 = 0b_00000000_00010000_00000000_00000000;
internal const int Bit19 = 0b_00000000_00001000_00000000_00000000;
internal const int Bit18 = 0b_00000000_00000100_00000000_00000000;
internal const int Bit17 = 0b_00000000_00000010_00000000_00000000;
internal const int Bit16 = 0b_00000000_00000001_00000000_00000000;
internal const int Bit15 = 0b_00000000_00000000_10000000_00000000;
internal const int Bit14 = 0b_00000000_00000000_01000000_00000000;
internal const int Bit13 = 0b_00000000_00000000_00100000_00000000;
internal const int Bit12 = 0b_00000000_00000000_00010000_00000000;
internal const int Bit11 = 0b_00000000_00000000_00001000_00000000;
internal const int Bit10 = 0b_00000000_00000000_00000100_00000000;
internal const int Bit09 = 0b_00000000_00000000_00000010_00000000;
internal const int Bit08 = 0b_00000000_00000000_00000001_00000000;
internal const int Bit07 = 0b_00000000_00000000_00000000_10000000;
internal const int Bit06 = 0b_00000000_00000000_00000000_01000000;
internal const int Bit05 = 0b_00000000_00000000_00000000_00100000;
internal const int Bit04 = 0b_00000000_00000000_00000000_00010000;
internal const int Bit03 = 0b_00000000_00000000_00000000_00001000;
internal const int Bit02 = 0b_00000000_00000000_00000000_00000100;
internal const int Bit01 = 0b_00000000_00000000_00000000_00000010;
internal const int Bit00 = 0b_00000000_00000000_00000000_00000001;
internal const int All_0 = 0;
internal const int All_1 = ~All_0;
internal const int Alternating_01 = 0b_01010101_01010101_01010101_01010101;
internal const int Alternating_10 = ~Alternating_01;
internal const int EvenBytesHigh = 0b_00000000_11111111_00000000_11111111;
internal const int OddBytesHigh = ~EvenBytesHigh;
}

View File

@@ -0,0 +1,49 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Same as <see cref="BasicEnum"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute"/> applied.
/// </summary>
[GenerateEnumExtensionMethods]
public enum BetterEnum
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = 0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = 0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = 0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = ~All_0,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = ~Alternating_01,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
OddBytesHigh = ~EvenBytesHigh,
}

View File

@@ -0,0 +1,49 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Same as <see cref="BasicEnum_ExplicitInt"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute"/> applied.
/// </summary>
[GenerateEnumExtensionMethods]
public enum BetterEnum_ExplicitInt
{
Bit31 = BasicEnum_ExplicitInt.Bit31,
Bit30 = BasicEnum_ExplicitInt.Bit30,
Bit29 = BasicEnum_ExplicitInt.Bit29,
Bit28 = BasicEnum_ExplicitInt.Bit28,
Bit27 = BasicEnum_ExplicitInt.Bit27,
Bit26 = BasicEnum_ExplicitInt.Bit26,
Bit25 = BasicEnum_ExplicitInt.Bit25,
Bit24 = BasicEnum_ExplicitInt.Bit24,
Bit23 = BasicEnum_ExplicitInt.Bit23,
Bit22 = BasicEnum_ExplicitInt.Bit22,
Bit21 = BasicEnum_ExplicitInt.Bit21,
Bit20 = BasicEnum_ExplicitInt.Bit20,
Bit19 = BasicEnum_ExplicitInt.Bit19,
Bit18 = BasicEnum_ExplicitInt.Bit18,
Bit17 = BasicEnum_ExplicitInt.Bit17,
Bit16 = BasicEnum_ExplicitInt.Bit16,
Bit15 = BasicEnum_ExplicitInt.Bit15,
Bit14 = BasicEnum_ExplicitInt.Bit14,
Bit13 = BasicEnum_ExplicitInt.Bit13,
Bit12 = BasicEnum_ExplicitInt.Bit12,
Bit11 = BasicEnum_ExplicitInt.Bit11,
Bit10 = BasicEnum_ExplicitInt.Bit10,
Bit09 = BasicEnum_ExplicitInt.Bit09,
Bit08 = BasicEnum_ExplicitInt.Bit08,
Bit07 = BasicEnum_ExplicitInt.Bit07,
Bit06 = BasicEnum_ExplicitInt.Bit06,
Bit05 = BasicEnum_ExplicitInt.Bit05,
Bit04 = BasicEnum_ExplicitInt.Bit04,
Bit03 = BasicEnum_ExplicitInt.Bit03,
Bit02 = BasicEnum_ExplicitInt.Bit02,
Bit01 = BasicEnum_ExplicitInt.Bit01,
Bit00 = BasicEnum_ExplicitInt.Bit00,
All_0 = BasicEnum_ExplicitInt.All_0,
All_1 = BasicEnum_ExplicitInt.All_1,
Alternating_01 = BasicEnum_ExplicitInt.Alternating_01,
Alternating_10 = BasicEnum_ExplicitInt.Alternating_10,
EvenBytesHigh = BasicEnum_ExplicitInt.EvenBytesHigh,
OddBytesHigh = BasicEnum_ExplicitInt.OddBytesHigh
}

View File

@@ -0,0 +1,50 @@
// ReSharper disable EnumUnderlyingTypeIsInt
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Same as <see cref="BetterEnum_ExplicitInt"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute.FastIsDefined"/> = <see langword="false" />.
/// </summary>
[GenerateEnumExtensionMethods (FastIsDefined = false)]
public enum BetterEnum_ExplicitInt_NoFastIsDefined : int
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = 0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = 0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = 0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = ~All_0,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = ~Alternating_01,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
OddBytesHigh = ~EvenBytesHigh,
}

View File

@@ -0,0 +1,49 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Same as <see cref="BasicEnum_ExplicitUInt"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute"/> applied.
/// </summary>
[GenerateEnumExtensionMethods]
public enum BetterEnum_ExplicitUInt : uint
{
Bit31 = 0b_10000000_00000000_00000000_00000000u,
Bit30 = 0b_01000000_00000000_00000000_00000000u,
Bit29 = 0b_00100000_00000000_00000000_00000000u,
Bit28 = 0b_00010000_00000000_00000000_00000000u,
Bit27 = 0b_00001000_00000000_00000000_00000000u,
Bit26 = 0b_00000100_00000000_00000000_00000000u,
Bit25 = 0b_00000010_00000000_00000000_00000000u,
Bit24 = 0b_00000001_00000000_00000000_00000000u,
Bit23 = 0b_00000000_10000000_00000000_00000000u,
Bit22 = 0b_00000000_01000000_00000000_00000000u,
Bit21 = 0b_00000000_00100000_00000000_00000000u,
Bit20 = 0b_00000000_00010000_00000000_00000000u,
Bit19 = 0b_00000000_00001000_00000000_00000000u,
Bit18 = 0b_00000000_00000100_00000000_00000000u,
Bit17 = 0b_00000000_00000010_00000000_00000000u,
Bit16 = 0b_00000000_00000001_00000000_00000000u,
Bit15 = 0b_00000000_00000000_10000000_00000000u,
Bit14 = 0b_00000000_00000000_01000000_00000000u,
Bit13 = 0b_00000000_00000000_00100000_00000000u,
Bit12 = 0b_00000000_00000000_00010000_00000000u,
Bit11 = 0b_00000000_00000000_00001000_00000000u,
Bit10 = 0b_00000000_00000000_00000100_00000000u,
Bit09 = 0b_00000000_00000000_00000010_00000000u,
Bit08 = 0b_00000000_00000000_00000001_00000000u,
Bit07 = 0b_00000000_00000000_00000000_10000000u,
Bit06 = 0b_00000000_00000000_00000000_01000000u,
Bit05 = 0b_00000000_00000000_00000000_00100000u,
Bit04 = 0b_00000000_00000000_00000000_00010000u,
Bit03 = 0b_00000000_00000000_00000000_00001000u,
Bit02 = 0b_00000000_00000000_00000000_00000100u,
Bit01 = 0b_00000000_00000000_00000000_00000010u,
Bit00 = 0b_00000000_00000000_00000000_00000001u,
All_0 = 0,
All_1 = ~All_0,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = ~Alternating_01,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
OddBytesHigh = ~EvenBytesHigh,
}

View File

@@ -0,0 +1,49 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Same as <see cref="BetterEnum_ExplicitUInt"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute.FastIsDefined"/> = <see langword="false" />.
/// </summary>
[GenerateEnumExtensionMethods (FastIsDefined = false)]
public enum BetterEnum_ExplicitUInt_NoFastIsDefined : uint
{
Bit31 = 0b_10000000_00000000_00000000_00000000u,
Bit30 = 0b_01000000_00000000_00000000_00000000u,
Bit29 = 0b_00100000_00000000_00000000_00000000u,
Bit28 = 0b_00010000_00000000_00000000_00000000u,
Bit27 = 0b_00001000_00000000_00000000_00000000u,
Bit26 = 0b_00000100_00000000_00000000_00000000u,
Bit25 = 0b_00000010_00000000_00000000_00000000u,
Bit24 = 0b_00000001_00000000_00000000_00000000u,
Bit23 = 0b_00000000_10000000_00000000_00000000u,
Bit22 = 0b_00000000_01000000_00000000_00000000u,
Bit21 = 0b_00000000_00100000_00000000_00000000u,
Bit20 = 0b_00000000_00010000_00000000_00000000u,
Bit19 = 0b_00000000_00001000_00000000_00000000u,
Bit18 = 0b_00000000_00000100_00000000_00000000u,
Bit17 = 0b_00000000_00000010_00000000_00000000u,
Bit16 = 0b_00000000_00000001_00000000_00000000u,
Bit15 = 0b_00000000_00000000_10000000_00000000u,
Bit14 = 0b_00000000_00000000_01000000_00000000u,
Bit13 = 0b_00000000_00000000_00100000_00000000u,
Bit12 = 0b_00000000_00000000_00010000_00000000u,
Bit11 = 0b_00000000_00000000_00001000_00000000u,
Bit10 = 0b_00000000_00000000_00000100_00000000u,
Bit09 = 0b_00000000_00000000_00000010_00000000u,
Bit08 = 0b_00000000_00000000_00000001_00000000u,
Bit07 = 0b_00000000_00000000_00000000_10000000u,
Bit06 = 0b_00000000_00000000_00000000_01000000u,
Bit05 = 0b_00000000_00000000_00000000_00100000u,
Bit04 = 0b_00000000_00000000_00000000_00010000u,
Bit03 = 0b_00000000_00000000_00000000_00001000u,
Bit02 = 0b_00000000_00000000_00000000_00000100u,
Bit01 = 0b_00000000_00000000_00000000_00000010u,
Bit00 = 0b_00000000_00000000_00000000_00000001u,
All_0 = 0,
All_1 = ~All_0,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = ~Alternating_01,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
OddBytesHigh = ~EvenBytesHigh,
}

View File

@@ -0,0 +1,49 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Same as <see cref="BetterEnum"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute.FastIsDefined"/> = <see langword="false" />.
/// </summary>
[GenerateEnumExtensionMethods (FastIsDefined = false)]
public enum BetterEnum_NoFastIsDefined
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = 0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = 0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = 0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = ~All_0,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = ~Alternating_01,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
OddBytesHigh = ~EvenBytesHigh,
}

View File

@@ -0,0 +1,50 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Same as <see cref="FlagsEnum"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute"/> applied.
/// </summary>
[Flags]
[GenerateEnumExtensionMethods]
public enum BetterFlagsEnum
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = -0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = -0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = -0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = ~All_0,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = ~Alternating_01,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
OddBytesHigh = ~EvenBytesHigh,
}

View File

@@ -0,0 +1,51 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// <summary>
/// Same as <see cref="FlagsEnum_ExplicitInt"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute"/> applied.
/// </summary>
[Flags]
[GenerateEnumExtensionMethods]
public enum BetterFlagsEnum_ExplicitInt : int
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = -0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = -0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = -0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = ~All_0,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = ~Alternating_01,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
OddBytesHigh = ~EvenBytesHigh,
}

View File

@@ -0,0 +1,50 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Same as <see cref="FlagsEnum_ExplicitUInt"/>, but with <see cref="GenerateEnumExtensionMethodsAttribute"/> applied.
/// </summary>
[Flags]
[GenerateEnumExtensionMethods]
public enum BetterFlagsEnum_ExplicitUInt : uint
{
Bit31 = 0b_10000000_00000000_00000000_00000000u,
Bit30 = 0b_01000000_00000000_00000000_00000000u,
Bit29 = 0b_00100000_00000000_00000000_00000000u,
Bit28 = 0b_00010000_00000000_00000000_00000000u,
Bit27 = 0b_00001000_00000000_00000000_00000000u,
Bit26 = 0b_00000100_00000000_00000000_00000000u,
Bit25 = 0b_00000010_00000000_00000000_00000000u,
Bit24 = 0b_00000001_00000000_00000000_00000000u,
Bit23 = 0b_00000000_10000000_00000000_00000000u,
Bit22 = 0b_00000000_01000000_00000000_00000000u,
Bit21 = 0b_00000000_00100000_00000000_00000000u,
Bit20 = 0b_00000000_00010000_00000000_00000000u,
Bit19 = 0b_00000000_00001000_00000000_00000000u,
Bit18 = 0b_00000000_00000100_00000000_00000000u,
Bit17 = 0b_00000000_00000010_00000000_00000000u,
Bit16 = 0b_00000000_00000001_00000000_00000000u,
Bit15 = 0b_00000000_00000000_10000000_00000000u,
Bit14 = 0b_00000000_00000000_01000000_00000000u,
Bit13 = 0b_00000000_00000000_00100000_00000000u,
Bit12 = 0b_00000000_00000000_00010000_00000000u,
Bit11 = 0b_00000000_00000000_00001000_00000000u,
Bit10 = 0b_00000000_00000000_00000100_00000000u,
Bit09 = 0b_00000000_00000000_00000010_00000000u,
Bit08 = 0b_00000000_00000000_00000001_00000000u,
Bit07 = 0b_00000000_00000000_00000000_10000000u,
Bit06 = 0b_00000000_00000000_00000000_01000000u,
Bit05 = 0b_00000000_00000000_00000000_00100000u,
Bit04 = 0b_00000000_00000000_00000000_00010000u,
Bit03 = 0b_00000000_00000000_00000000_00001000u,
Bit02 = 0b_00000000_00000000_00000000_00000100u,
Bit01 = 0b_00000000_00000000_00000000_00000010u,
Bit00 = 0b_00000000_00000000_00000000_00000001u,
All_0 = 0,
All_1 = ~All_0,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = ~Alternating_01,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
OddBytesHigh = ~EvenBytesHigh,
}

View File

@@ -0,0 +1,46 @@
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Basic enum without explicitly-defined backing type and no attributes on the enum or any of its members.
/// </summary>
public enum BasicEnum
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = 0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = 0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = 0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = -1,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = unchecked((int)0b_10101010_10101010_10101010_10101010),
OddBytesHigh = unchecked((int)0b_11111111_00000000_11111111_00000000),
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111,
}

View File

@@ -0,0 +1,48 @@
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Basic enum with explicitly-defined backing type of int and no attributes on the enum or any of its members.
/// </summary>
public enum BasicEnum_ExplicitInt : int
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = 0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = 0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = 0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = -1,
Alternating_01 = 0b_01010101_01010101_01010101_01010101,
Alternating_10 = unchecked((int)0b_10101010_10101010_10101010_10101010),
OddBytesHigh = unchecked((int)0b_11111111_00000000_11111111_00000000),
EvenBytesHigh = unchecked((int)0b_00000000_11111111_00000000_11111111),
}

View File

@@ -0,0 +1,46 @@
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Basic enum with explicitly-defined backing type of uint and no attributes on the enum or any of its members.
/// </summary>
public enum BasicEnum_ExplicitUInt : uint
{
Bit31 = 0b_10000000_00000000_00000000_00000000u,
Bit30 = 0b_01000000_00000000_00000000_00000000u,
Bit29 = 0b_00100000_00000000_00000000_00000000u,
Bit28 = 0b_00010000_00000000_00000000_00000000u,
Bit27 = 0b_00001000_00000000_00000000_00000000u,
Bit26 = 0b_00000100_00000000_00000000_00000000u,
Bit25 = 0b_00000010_00000000_00000000_00000000u,
Bit24 = 0b_00000001_00000000_00000000_00000000u,
Bit23 = 0b_00000000_10000000_00000000_00000000u,
Bit22 = 0b_00000000_01000000_00000000_00000000u,
Bit21 = 0b_00000000_00100000_00000000_00000000u,
Bit20 = 0b_00000000_00010000_00000000_00000000u,
Bit19 = 0b_00000000_00001000_00000000_00000000u,
Bit18 = 0b_00000000_00000100_00000000_00000000u,
Bit17 = 0b_00000000_00000010_00000000_00000000u,
Bit16 = 0b_00000000_00000001_00000000_00000000u,
Bit15 = 0b_00000000_00000000_10000000_00000000u,
Bit14 = 0b_00000000_00000000_01000000_00000000u,
Bit13 = 0b_00000000_00000000_00100000_00000000u,
Bit12 = 0b_00000000_00000000_00010000_00000000u,
Bit11 = 0b_00000000_00000000_00001000_00000000u,
Bit10 = 0b_00000000_00000000_00000100_00000000u,
Bit09 = 0b_00000000_00000000_00000010_00000000u,
Bit08 = 0b_00000000_00000000_00000001_00000000u,
Bit07 = 0b_00000000_00000000_00000000_10000000u,
Bit06 = 0b_00000000_00000000_00000000_01000000u,
Bit05 = 0b_00000000_00000000_00000000_00100000u,
Bit04 = 0b_00000000_00000000_00000000_00010000u,
Bit03 = 0b_00000000_00000000_00000000_00001000u,
Bit02 = 0b_00000000_00000000_00000000_00000100u,
Bit01 = 0b_00000000_00000000_00000000_00000010u,
Bit00 = 0b_00000000_00000000_00000000_00000001u,
All_0 = 0b_00000000_00000000_00000000_00000000u,
All_1 = 0b_11111111_11111111_11111111_11111111u,
Alternating_01 = 0b_01010101_01010101_01010101_01010101u,
Alternating_10 = 0b_10101010_10101010_10101010_10101010u,
OddBytesHigh = 0b_11111111_00000000_11111111_00000000u,
EvenBytesHigh = 0b_00000000_11111111_00000000_11111111u,
}

View File

@@ -0,0 +1,43 @@
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Flags enum without explicitly-defined backing type and only a <see cref="FlagsAttribute"/> on the enum declaration No other attributes on the enum or its members..
/// </summary>
[Flags]
public enum FlagsEnum
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = -0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = -0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = -0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = -1
}

View File

@@ -0,0 +1,43 @@
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Flags enum with explicitly-defined backing type of int and only a <see cref="FlagsAttribute"/> on the enum declaration No other attributes on the enum or its members..
/// </summary>
[Flags]
public enum FlagsEnum_ExplicitInt : int
{
Bit31 = -0b_10000000_00000000_00000000_00000000,
Bit30 = 0b_01000000_00000000_00000000_00000000,
Bit29 = 0b_00100000_00000000_00000000_00000000,
Bit28 = 0b_00010000_00000000_00000000_00000000,
Bit27 = 0b_00001000_00000000_00000000_00000000,
Bit26 = 0b_00000100_00000000_00000000_00000000,
Bit25 = 0b_00000010_00000000_00000000_00000000,
Bit24 = 0b_00000001_00000000_00000000_00000000,
Bit23 = -0b_00000000_10000000_00000000_00000000,
Bit22 = 0b_00000000_01000000_00000000_00000000,
Bit21 = 0b_00000000_00100000_00000000_00000000,
Bit20 = 0b_00000000_00010000_00000000_00000000,
Bit19 = 0b_00000000_00001000_00000000_00000000,
Bit18 = 0b_00000000_00000100_00000000_00000000,
Bit17 = 0b_00000000_00000010_00000000_00000000,
Bit16 = 0b_00000000_00000001_00000000_00000000,
Bit15 = -0b_00000000_00000000_10000000_00000000,
Bit14 = 0b_00000000_00000000_01000000_00000000,
Bit13 = 0b_00000000_00000000_00100000_00000000,
Bit12 = 0b_00000000_00000000_00010000_00000000,
Bit11 = 0b_00000000_00000000_00001000_00000000,
Bit10 = 0b_00000000_00000000_00000100_00000000,
Bit09 = 0b_00000000_00000000_00000010_00000000,
Bit08 = 0b_00000000_00000000_00000001_00000000,
Bit07 = -0b_00000000_00000000_00000000_10000000,
Bit06 = 0b_00000000_00000000_00000000_01000000,
Bit05 = 0b_00000000_00000000_00000000_00100000,
Bit04 = 0b_00000000_00000000_00000000_00010000,
Bit03 = 0b_00000000_00000000_00000000_00001000,
Bit02 = 0b_00000000_00000000_00000000_00000100,
Bit01 = 0b_00000000_00000000_00000000_00000010,
Bit00 = 0b_00000000_00000000_00000000_00000001,
All_0 = 0,
All_1 = -1
}

View File

@@ -0,0 +1,43 @@
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions.EnumDefinitions;
/// <summary>
/// Flags enum with explicitly-defined backing type of uint and only a <see cref="FlagsAttribute"/> on the enum declaration No other attributes on the enum or its members..
/// </summary>
[Flags]
public enum FlagsEnum_ExplicitUInt : uint
{
Bit31 = 0b_10000000_00000000_00000000_00000000u,
Bit30 = 0b_01000000_00000000_00000000_00000000u,
Bit29 = 0b_00100000_00000000_00000000_00000000u,
Bit28 = 0b_00010000_00000000_00000000_00000000u,
Bit27 = 0b_00001000_00000000_00000000_00000000u,
Bit26 = 0b_00000100_00000000_00000000_00000000u,
Bit25 = 0b_00000010_00000000_00000000_00000000u,
Bit24 = 0b_00000001_00000000_00000000_00000000u,
Bit23 = 0b_00000000_10000000_00000000_00000000u,
Bit22 = 0b_00000000_01000000_00000000_00000000u,
Bit21 = 0b_00000000_00100000_00000000_00000000u,
Bit20 = 0b_00000000_00010000_00000000_00000000u,
Bit19 = 0b_00000000_00001000_00000000_00000000u,
Bit18 = 0b_00000000_00000100_00000000_00000000u,
Bit17 = 0b_00000000_00000010_00000000_00000000u,
Bit16 = 0b_00000000_00000001_00000000_00000000u,
Bit15 = 0b_00000000_00000000_10000000_00000000u,
Bit14 = 0b_00000000_00000000_01000000_00000000u,
Bit13 = 0b_00000000_00000000_00100000_00000000u,
Bit12 = 0b_00000000_00000000_00010000_00000000u,
Bit11 = 0b_00000000_00000000_00001000_00000000u,
Bit10 = 0b_00000000_00000000_00000100_00000000u,
Bit09 = 0b_00000000_00000000_00000010_00000000u,
Bit08 = 0b_00000000_00000000_00000001_00000000u,
Bit07 = 0b_00000000_00000000_00000000_10000000u,
Bit06 = 0b_00000000_00000000_00000000_01000000u,
Bit05 = 0b_00000000_00000000_00000000_00100000u,
Bit04 = 0b_00000000_00000000_00000000_00010000u,
Bit03 = 0b_00000000_00000000_00000000_00001000u,
Bit02 = 0b_00000000_00000000_00000000_00000100u,
Bit01 = 0b_00000000_00000000_00000000_00000010u,
Bit00 = 0b_00000000_00000000_00000000_00000001u,
All_0 = 0b_00000000_00000000_00000000_00000000u,
All_1 = 0b_11111111_11111111_11111111_11111111u
}

View File

@@ -0,0 +1,329 @@
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;
using System.Runtime.CompilerServices;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using Terminal.Gui.Analyzers.Internal.Attributes;
using Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions;
[TestFixture]
[Category ("Source Generators")]
[TestOf (typeof (EnumExtensionMethodsIncrementalGenerator))]
[Parallelizable (ParallelScope.Children)]
public class EnumExtensionMethodsIncrementalGeneratorTests
{
private static bool _isInitialized;
/// <summary>All enum types declared in the test assembly.</summary>
private static readonly ObservableCollection<Type> AllEnumTypes = [];
/// <summary>
/// All enum types without a <see cref="GenerateEnumExtensionMethodsAttribute"/>, <see cref="AllEnumTypes"/>
/// </summary>
private static readonly HashSet<Type> BoringEnumTypes = [];
/// <summary>All extension classes generated for enums with our attribute.</summary>
private static readonly ObservableCollection<Type> EnumExtensionClasses = [];
private static readonly ConcurrentDictionary<Type, EnumData> ExtendedEnumTypeMappings = [];
private static IEnumerable<Type> ExtendedEnumTypes => ExtendedEnumTypeMappings.Keys;
private static readonly ReaderWriterLockSlim InitializationLock = new ();
private static IEnumerable<AssemblyExtendedEnumTypeAttribute> GetAssemblyExtendedEnumTypeAttributes () =>
Assembly.GetExecutingAssembly ()
.GetCustomAttributes<AssemblyExtendedEnumTypeAttribute> ();
private static IEnumerable<TestCaseData> Get_AssemblyExtendedEnumTypeAttribute_EnumHasGeneratorAttribute_Cases ()
{
return GetAssemblyExtendedEnumTypeAttributes ()
.Select (
static attr => new TestCaseData (attr)
{
TestName = $"{nameof (AssemblyExtendedEnumTypeAttribute_EnumHasGeneratorAttribute)}({attr.EnumType.Name},{attr.ExtensionClass.Name})",
HasExpectedResult = true,
ExpectedResult = true
});
}
[Test]
[Category ("Attributes")]
[TestCaseSource (nameof (Get_AssemblyExtendedEnumTypeAttribute_EnumHasGeneratorAttribute_Cases))]
public bool AssemblyExtendedEnumTypeAttribute_EnumHasGeneratorAttribute (AssemblyExtendedEnumTypeAttribute attr)
{
Assume.That (attr, Is.Not.Null);
Assume.That (attr.EnumType, Is.Not.Null);
Assume.That (attr.EnumType!.IsEnum);
return attr.EnumType.IsDefined (typeof (GenerateEnumExtensionMethodsAttribute));
}
private const string AssemblyExtendedEnumTypeAttributeEnumPropertyName =
$"{nameof (AssemblyExtendedEnumTypeAttribute)}.{nameof (AssemblyExtendedEnumTypeAttribute.EnumType)}";
[Test]
[Category("Attributes")]
public void AssemblyExtendedEnumTypeAttribute_ExtensionClassHasExpectedReverseMappingAttribute ([ValueSource(nameof(GetAssemblyExtendedEnumTypeAttributes))]AssemblyExtendedEnumTypeAttribute attr)
{
Assume.That (attr, Is.Not.Null);
Assume.That (attr.ExtensionClass, Is.Not.Null);
Assume.That (attr.ExtensionClass!.IsClass);
Assume.That (attr.ExtensionClass!.IsSealed);
Assert.That (attr.ExtensionClass.IsDefined (typeof (ExtensionsForEnumTypeAttribute<>)));
}
[Test]
[Category("Attributes")]
public void ExtendedEnum_AssemblyHasMatchingAttribute ([ValueSource(nameof(GetExtendedEnum_EnumData))]EnumData enumData)
{
Assume.That (enumData, Is.Not.Null);
Assume.That (enumData.EnumType, Is.Not.Null);
Assume.That (enumData.EnumType!.IsEnum);
Assert.That (enumData.EnumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
}
[Test]
public void BoringEnum_DoesNotHaveExtensions ([ValueSource (nameof (BoringEnumTypes))] Type enumType)
{
Assume.That (enumType.IsEnum);
Assert.That (enumType, Has.No.Attribute<GenerateEnumExtensionMethodsAttribute> ());
}
[Test]
public void ExtendedEnum_FastIsDefinedFalse_DoesNotHaveFastIsDefined ([ValueSource (nameof (GetExtendedEnumTypes_FastIsDefinedFalse))] EnumData enumData)
{
Assume.That (enumData.EnumType.IsEnum);
Assume.That (enumData.EnumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
Assume.That (enumData.GeneratorAttribute, Is.Not.Null);
Assume.That (enumData.GeneratorAttribute, Is.EqualTo (enumData.EnumType.GetCustomAttribute<GenerateEnumExtensionMethodsAttribute> ()));
Assume.That (enumData.GeneratorAttribute, Has.Property ("FastIsDefined").False);
Assume.That (enumData.ExtensionClass, Is.Not.Null);
Assert.That (enumData.ExtensionClass!.GetMethod ("FastIsDefined"), Is.Null);
}
[Test]
public void ExtendedEnum_StaticExtensionClassExists ([ValueSource (nameof (ExtendedEnumTypes))] Type enumType)
{
Assume.That (enumType.IsEnum);
Assume.That (enumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
ITypeInfo enumTypeInfo = new TypeWrapper (enumType);
Assume.That (enumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
}
[Test]
public void ExtendedEnum_FastIsDefinedTrue_HasFastIsDefined ([ValueSource (nameof (GetExtendedEnumTypes_FastIsDefinedTrue))] EnumData enumData)
{
Assume.That (enumData.EnumType, Is.Not.Null);
Assume.That (enumData.EnumType.IsEnum);
Assume.That (enumData.EnumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
Assume.That (enumData.ExtensionClass, Is.Not.Null);
ITypeInfo extensionClassTypeInfo = new TypeWrapper (enumData.ExtensionClass!);
Assume.That (extensionClassTypeInfo.IsStaticClass);
Assume.That (enumData.GeneratorAttribute, Is.Not.Null);
Assume.That (enumData.GeneratorAttribute, Is.EqualTo (enumData.EnumType.GetCustomAttribute<GenerateEnumExtensionMethodsAttribute> ()));
Assume.That (enumData.GeneratorAttribute, Has.Property ("FastIsDefined").True);
MethodInfo? fastIsDefinedMethod = enumData.ExtensionClass!.GetMethod ("FastIsDefined");
Assert.That (fastIsDefinedMethod, Is.Not.Null);
Assert.That (fastIsDefinedMethod, Has.Attribute<ExtensionAttribute> ());
IMethodInfo[] extensionMethods = extensionClassTypeInfo.GetMethodsWithAttribute<ExtensionAttribute> (false);
}
private static IEnumerable<EnumData> GetExtendedEnum_EnumData ()
{
InitializationLock.EnterUpgradeableReadLock ();
try
{
if (!_isInitialized)
{
Initialize ();
}
return ExtendedEnumTypeMappings.Values;
}
finally
{
InitializationLock.ExitUpgradeableReadLock ();
}
}
private static IEnumerable<Type> GetBoringEnumTypes ()
{
InitializationLock.EnterUpgradeableReadLock ();
try
{
if (!_isInitialized)
{
Initialize ();
}
return BoringEnumTypes;
}
finally
{
InitializationLock.ExitUpgradeableReadLock ();
}
}
private static IEnumerable<EnumData> GetExtendedEnumTypes_FastIsDefinedFalse ()
{
InitializationLock.EnterUpgradeableReadLock ();
try
{
if (!_isInitialized)
{
Initialize ();
}
return ExtendedEnumTypeMappings.Values.Where (static t => t.GeneratorAttribute?.FastIsDefined is false);
}
finally
{
InitializationLock.ExitUpgradeableReadLock ();
}
}
private static IEnumerable<EnumData> GetExtendedEnumTypes_FastIsDefinedTrue ()
{
InitializationLock.EnterUpgradeableReadLock ();
try
{
if (!_isInitialized)
{
Initialize ();
}
return ExtendedEnumTypeMappings.Values.Where (static t => t.GeneratorAttribute?.FastIsDefined is true);
}
finally
{
InitializationLock.ExitUpgradeableReadLock ();
}
}
private static void Initialize ()
{
if (!InitializationLock.IsUpgradeableReadLockHeld || !InitializationLock.TryEnterWriteLock (5000))
{
return;
}
try
{
if (_isInitialized)
{
return;
}
AllEnumTypes.CollectionChanged += AllEnumTypes_CollectionChanged;
EnumExtensionClasses.CollectionChanged += EnumExtensionClasses_OnCollectionChanged;
Type [] allAssemblyTypes = Assembly
.GetExecutingAssembly ()
.GetTypes ();
IEnumerable<Type> allEnumTypes = allAssemblyTypes.Where (IsDefinedEnum);
foreach (Type type in allEnumTypes)
{
AllEnumTypes.Add (type);
}
foreach (Type type in allAssemblyTypes.Where (static t => t.IsClass && t.IsDefined (typeof (ExtensionsForEnumTypeAttribute<>))))
{
EnumExtensionClasses.Add (type);
}
_isInitialized = true;
}
finally
{
InitializationLock.ExitWriteLock ();
}
return;
static bool IsDefinedEnum (Type t) { return t is { IsEnum: true, IsGenericType: false, IsConstructedGenericType: false, IsTypeDefinition: true }; }
static void AllEnumTypes_CollectionChanged (object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action is not NotifyCollectionChangedAction.Add and not NotifyCollectionChangedAction.Replace || e.NewItems is null)
{
return;
}
foreach (Type enumType in e.NewItems.OfType<Type> ())
{
if (enumType.GetCustomAttribute<GenerateEnumExtensionMethodsAttribute> () is not { } generatorAttribute)
{
BoringEnumTypes.Add (enumType);
continue;
}
ExtendedEnumTypeMappings.AddOrUpdate (
enumType,
CreateNewEnumData,
UpdateGeneratorAttributeProperty,
generatorAttribute);
}
}
static EnumData CreateNewEnumData (Type tEnum, GenerateEnumExtensionMethodsAttribute attr) { return new (tEnum, attr); }
static EnumData UpdateGeneratorAttributeProperty (Type tEnum, EnumData data, GenerateEnumExtensionMethodsAttribute attr)
{
data.GeneratorAttribute ??= attr;
return data;
}
static void EnumExtensionClasses_OnCollectionChanged (object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Add)
{
return;
}
foreach (Type extensionClassType in e.NewItems!.OfType<Type> ())
{
if (extensionClassType.GetCustomAttribute (typeof (ExtensionsForEnumTypeAttribute<>), false) is not IExtensionsForEnumTypeAttributes
{
EnumType.IsEnum: true
} extensionForAttribute)
{
continue;
}
ExtendedEnumTypeMappings [extensionForAttribute.EnumType].ExtensionClass ??= extensionClassType;
}
}
}
public sealed record EnumData (
Type EnumType,
GenerateEnumExtensionMethodsAttribute? GeneratorAttribute = null,
Type? ExtensionClass = null,
IExtensionsForEnumTypeAttributes? ExtensionForEnumTypeAttribute = null)
{
public Type? ExtensionClass { get; set; } = ExtensionClass;
public IExtensionsForEnumTypeAttributes? ExtensionForEnumTypeAttribute { get; set; } = ExtensionForEnumTypeAttribute;
public GenerateEnumExtensionMethodsAttribute? GeneratorAttribute { get; set; } = GeneratorAttribute;
}
}

View File

@@ -0,0 +1,111 @@
using System.CodeDom.Compiler;
using System.Text;
namespace Terminal.Gui.Analyzers.Internal.Tests;
[TestFixture]
[Category ("Extension Methods")]
[TestOf (typeof (IndentedTextWriterExtensions))]
[Parallelizable (ParallelScope.Children)]
public class IndentedTextWriterExtensionsTests
{
[Test]
public void Pop_Decrements ()
{
StringBuilder sb = new (0);
using var sw = new StringWriter (sb);
using var writer = new IndentedTextWriter (sw);
writer.Indent = 5;
Assume.That (writer.Indent, Is.EqualTo (5));
writer.Pop ();
Assert.That (writer.Indent, Is.EqualTo (4));
}
[Test]
public void Pop_WithClosing_WritesAndPops ([Values ("}", ")", "]")] string scopeClosing)
{
StringBuilder sb = new (256);
using var sw = new StringWriter (sb);
using var writer = new IndentedTextWriter (sw, " ");
writer.Indent = 5;
writer.Flush ();
Assume.That (writer.Indent, Is.EqualTo (5));
Assume.That (sb.Length, Is.Zero);
// Need to write something first, or IndentedTextWriter won't emit the indentation for the first call.
// So we'll write an empty line.
writer.WriteLine ();
for (ushort indentCount = 5; indentCount > 0;)
{
writer.Pop (scopeClosing);
Assert.That (writer.Indent, Is.EqualTo (--indentCount));
}
writer.Flush ();
var result = sb.ToString ();
Assert.That (
result,
Is.EqualTo (
$"""
{scopeClosing}
{scopeClosing}
{scopeClosing}
{scopeClosing}
{scopeClosing}
"""));
}
[Test]
public void Push_Increments ()
{
StringBuilder sb = new (32);
using var sw = new StringWriter (sb);
using var writer = new IndentedTextWriter (sw, " ");
for (int indentCount = 0; indentCount < 5; indentCount++)
{
writer.Push ();
Assert.That (writer.Indent, Is.EqualTo (indentCount + 1));
}
}
[Test]
public void Push_WithOpening_WritesAndPushes ([Values ('{', '(', '[')] char scopeOpening)
{
StringBuilder sb = new (256);
using var sw = new StringWriter (sb);
using var writer = new IndentedTextWriter (sw, " ");
for (ushort indentCount = 0; indentCount < 5;)
{
writer.Push ("Opening UninterestingEnum", scopeOpening);
Assert.That (writer.Indent, Is.EqualTo (++indentCount));
}
writer.Flush ();
var result = sb.ToString ();
Assert.That (
result,
Is.EqualTo (
$"""
Opening UninterestingEnum
{scopeOpening}
Opening UninterestingEnum
{scopeOpening}
Opening UninterestingEnum
{scopeOpening}
Opening UninterestingEnum
{scopeOpening}
Opening UninterestingEnum
{scopeOpening}
"""));
}
}

View File

@@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineTrace>True</DefineTrace>
<DebugType>portable</DebugType>
<DefineConstants>$(DefineConstants);JETBRAINS_ANNOTATIONS;CONTRACTS_FULL;CODE_ANALYSIS</DefineConstants>
<ImplicitUsings>enable</ImplicitUsings>
<NoLogo>True</NoLogo>
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Terminal.Gui.Analyzers.Internal\Terminal.Gui.Analyzers.Internal.csproj">
<PrivateAssets>all</PrivateAssets>
<OutputItemType>Analyzer</OutputItemType>
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
<Using Include="Terminal.Gui" />
<Using Include="Terminal.Gui.Analyzers" />
<Using Include="Terminal.Gui.Analyzers.Internal" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generators_005Cenumextensions_005Cenumdefinitions_005Cwithgenerator/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generators_005Cenumextensions_005Cenumdefinitions_005Cwithoutgenerator/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -0,0 +1,20 @@
using Microsoft.CodeAnalysis;
namespace Terminal.Gui.Analyzers.Internal;
internal static class AccessibilityExtensions
{
internal static string ToCSharpString (this Accessibility value)
{
return value switch
{
Accessibility.Public => "public",
Accessibility.Internal => "internal",
Accessibility.Private => "private",
Accessibility.Protected => "protected",
Accessibility.ProtectedAndInternal => "private protected",
Accessibility.ProtectedOrInternal => "protected internal",
_ => string.Empty
};
}
}

View File

@@ -0,0 +1,8 @@
## Release 1.0
### New Rules
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
TG0001 | Usage | Error | TG0001_GlobalNamespaceNotSupported
TG0002 | Usage | Error | TG0002_UnderlyingTypeNotSupported

View File

@@ -0,0 +1,4 @@
### New Rules
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------

View File

@@ -0,0 +1,117 @@
#define JETBRAINS_ANNOTATIONS
using System.Collections.Immutable;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Terminal.Gui.Analyzers.Internal.Attributes;
using Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
namespace Terminal.Gui.Analyzers.Internal.Analyzers;
/// <summary>
/// Design-time analyzer that checks for proper use of <see cref="GenerateEnumExtensionMethodsAttribute"/>.
/// </summary>
[DiagnosticAnalyzer (LanguageNames.CSharp)]
[UsedImplicitly]
internal sealed class GenerateEnumExtensionMethodsAttributeAnalyzer : DiagnosticAnalyzer
{
// ReSharper disable once InconsistentNaming
private static readonly DiagnosticDescriptor TG0001_GlobalNamespaceNotSupported = new (
// ReSharper restore InconsistentNaming
"TG0001",
$"{nameof (GenerateEnumExtensionMethodsAttribute)} not supported on global enums",
"{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.",
"Usage",
DiagnosticSeverity.Error,
true,
null,
null,
WellKnownDiagnosticTags.NotConfigurable,
WellKnownDiagnosticTags.Compiler);
// ReSharper disable once InconsistentNaming
private static readonly DiagnosticDescriptor TG0002_UnderlyingTypeNotSupported = new (
"TG0002",
$"{nameof (GenerateEnumExtensionMethodsAttribute)} not supported for this enum type",
"{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.",
"Usage",
DiagnosticSeverity.Error,
true,
null,
null,
WellKnownDiagnosticTags.NotConfigurable,
WellKnownDiagnosticTags.Compiler);
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
[
TG0001_GlobalNamespaceNotSupported,
TG0002_UnderlyingTypeNotSupported
];
/// <inheritdoc/>
public override void Initialize (AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution ();
context.RegisterSyntaxNodeAction (CheckAttributeLocations, SyntaxKind.EnumDeclaration);
return;
static void CheckAttributeLocations (SyntaxNodeAnalysisContext analysisContext)
{
ISymbol? symbol = analysisContext.SemanticModel.GetDeclaredSymbol (analysisContext.Node) as INamedTypeSymbol;
if (symbol is not INamedTypeSymbol { EnumUnderlyingType: { } } enumSymbol)
{
// Somehow not even an enum declaration.
// Skip it.
return;
}
// Check attributes for those we care about and react accordingly.
foreach (AttributeData attributeData in enumSymbol.GetAttributes ())
{
if (attributeData.AttributeClass?.Name != nameof (GenerateEnumExtensionMethodsAttribute))
{
// Just skip - not an interesting attribute.
continue;
}
// Check enum underlying type for supported types (int and uint, currently)
// Report TG0002 if unsupported underlying type.
if (enumSymbol.EnumUnderlyingType is not { SpecialType: SpecialType.System_Int32 or SpecialType.System_UInt32 })
{
analysisContext.ReportDiagnostic (
Diagnostic.Create (
TG0002_UnderlyingTypeNotSupported,
enumSymbol.Locations.FirstOrDefault (),
enumSymbol.Name,
enumSymbol.EnumUnderlyingType.Name,
nameof (EnumExtensionMethodsIncrementalGenerator),
nameof (GenerateEnumExtensionMethodsAttribute)
)
);
}
// Check enum namespace (only non-global supported, currently)
// Report TG0001 if in the global namespace.
if (enumSymbol.ContainingSymbol is not INamespaceSymbol { IsGlobalNamespace: false })
{
analysisContext.ReportDiagnostic (
Diagnostic.Create (
TG0001_GlobalNamespaceNotSupported,
enumSymbol.Locations.FirstOrDefault (),
enumSymbol.Name,
nameof (EnumExtensionMethodsIncrementalGenerator),
nameof (GenerateEnumExtensionMethodsAttribute)
)
);
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
N:System.Runtime.CompilerServices
N:System.Diagnostics.CodeAnalysis
N:System.Numerics

View File

@@ -0,0 +1,26 @@
// ReSharper disable ClassNeverInstantiated.Global
#nullable enable
namespace Terminal.Gui.Analyzers.Internal.Attributes;
/// <summary>Assembly attribute declaring a known pairing of an <see langword="enum" /> type to an extension class.</summary>
/// <remarks>This attribute should only be written by internal source generators for Terminal.Gui. No other usage of any kind is supported.</remarks>
[System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class AssemblyExtendedEnumTypeAttribute : System.Attribute
{
/// <summary>Creates a new instance of <see cref="AssemblyExtendedEnumTypeAttribute" /> from the provided parameters.</summary>
/// <param name="enumType">The <see cref="System.Type" /> of an <see langword="enum" /> decorated with a <see cref="GenerateEnumExtensionMethodsAttribute" />.</param>
/// <param name="extensionClass">The <see cref="System.Type" /> of the <see langword="class" /> decorated with an <see cref="ExtensionsForEnumTypeAttribute{TEnum}" /> referring to the same type as <paramref name="enumType" />.</param>
public AssemblyExtendedEnumTypeAttribute (System.Type enumType, System.Type extensionClass)
{
EnumType = enumType;
ExtensionClass = extensionClass;
}
///<summary>An <see langword="enum" /> type that has been extended by Terminal.Gui source generators.</summary>
public System.Type EnumType { get; init; }
///<summary>A class containing extension methods for <see cref="EnumType"/>.</summary>
public System.Type ExtensionClass { get; init; }
/// <inheritdoc />
public override string ToString () => $"{EnumType.Name},{ExtensionClass.Name}";
}

View File

@@ -0,0 +1,22 @@
using System;
using JetBrains.Annotations;
namespace Terminal.Gui.Analyzers.Internal.Attributes;
/// <summary>
/// Designates an enum member for inclusion in generation of bitwise combinations with other members decorated with
/// this attribute which have the same <see cref="GroupTag"/> value.<br/>
/// </summary>
/// <remarks>
/// This attribute is only considered for members of enum types which have the
/// <see cref="GenerateEnumExtensionMethodsAttribute"/>.
/// </remarks>
[AttributeUsage (AttributeTargets.Field)]
[UsedImplicitly]
internal sealed class CombinationGroupingAttribute : Attribute
{
/// <summary>
/// Name of a group this member participates in, for FastHasFlags.
/// </summary>
public string GroupTag { get; set; }
}

View File

@@ -0,0 +1,37 @@
// ReSharper disable RedundantNameQualifier
// ReSharper disable RedundantNullableDirective
// ReSharper disable UnusedType.Global
#pragma warning disable IDE0001, IDE0240
#nullable enable
namespace Terminal.Gui.Analyzers.Internal.Attributes;
/// <summary>
/// Attribute written by the source generator for <see langword="enum" /> extension classes, for easier analysis and reflection.
/// </summary>
/// <remarks>
/// Properties are just convenient shortcuts to properties of <typeparamref name="TEnum"/>.
/// </remarks>
[System.AttributeUsage (System.AttributeTargets.Class | System.AttributeTargets.Interface)]
internal sealed class ExtensionsForEnumTypeAttribute<TEnum>: System.Attribute, IExtensionsForEnumTypeAttributes where TEnum : struct, System.Enum
{
/// <summary>
/// The namespace-qualified name of <typeparamref name="TEnum"/>.
/// </summary>
public string EnumFullName => EnumType.FullName!;
/// <summary>
/// The unqualified name of <typeparamref name="TEnum"/>.
/// </summary>
public string EnumName => EnumType.Name;
/// <summary>
/// The namespace containing <typeparamref name="TEnum"/>.
/// </summary>
public string EnumNamespace => EnumType.Namespace!;
/// <summary>
/// The <see cref="System.Type"/> given by <see langword="typeof"/>(<typeparamref name="TEnum"/>).
/// </summary>
public System.Type EnumType => typeof (TEnum);
}

View File

@@ -0,0 +1,110 @@
// ReSharper disable RedundantNullableDirective
// ReSharper disable RedundantUsingDirective
// ReSharper disable ClassNeverInstantiated.Global
#nullable enable
using System;
using Attribute = System.Attribute;
using AttributeUsageAttribute = System.AttributeUsageAttribute;
using AttributeTargets = System.AttributeTargets;
namespace Terminal.Gui.Analyzers.Internal.Attributes;
/// <summary>
/// Used to enable source generation of a common set of extension methods for enum types.
/// </summary>
[AttributeUsage (AttributeTargets.Enum)]
internal sealed class GenerateEnumExtensionMethodsAttribute : Attribute
{
/// <summary>
/// The name of the generated static class.
/// </summary>
/// <remarks>
/// If unspecified, null, empty, or only whitespace, defaults to the name of the enum plus "Extensions".<br/>
/// No other validation is performed, so illegal values will simply result in compiler errors.
/// <para>
/// Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
/// </para>
/// </remarks>
public string? ClassName { get; set; }
/// <summary>
/// The namespace in which to place the generated static class containing the extension methods.
/// </summary>
/// <remarks>
/// If unspecified, null, empty, or only whitespace, defaults to the namespace of the enum.<br/>
/// No other validation is performed, so illegal values will simply result in compiler errors.
/// <para>
/// Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
/// </para>
/// </remarks>
public string? ClassNamespace { get; set; }
/// <summary>
/// Whether to generate a fast, zero-allocation, non-boxing, and reflection-free alternative to the built-in
/// <see cref="Enum.HasFlag"/> method.
/// </summary>
/// <remarks>
/// <para>
/// Default: false
/// </para>
/// <para>
/// If the enum is not decorated with <see cref="FlagsAttribute"/>, this option has no effect.
/// </para>
/// <para>
/// If multiple members have the same value, the first member with that value will be used and subsequent members
/// with the same value will be skipped.
/// </para>
/// <para>
/// Overloads taking the enum type itself as well as the underlying type of the enum will be generated, enabling
/// avoidance of implicit or explicit cast overhead.
/// </para>
/// <para>
/// Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
/// </para>
/// </remarks>
public bool FastHasFlags { get; set; }
/// <summary>
/// Whether to generate a fast, zero-allocation, and reflection-free alternative to the built-in
/// <see cref="Enum.IsDefined"/> method,
/// using a switch expression as a hard-coded reverse mapping of numeric values to explicitly-named members.
/// </summary>
/// <remarks>
/// <para>
/// Default: true
/// </para>
/// <para>
/// If multiple members have the same value, the first member with that value will be used and subsequent members
/// with the same value will be skipped.
/// </para>
/// <para>
/// As with <see cref="Enum.IsDefined"/> the source generator only considers explicitly-named members.<br/>
/// Generation of values which represent valid bitwise combinations of members of enums decorated with
/// <see cref="FlagsAttribute"/> is not affected by this property.
/// </para>
/// </remarks>
public bool FastIsDefined { get; init; } = true;
/// <summary>
/// Gets a <see langword="bool"/> value indicating if this <see cref="GenerateEnumExtensionMethodsAttribute"/> instance
/// contains default values only. See <see href="#remarks">remarks</see> of this method or documentation on properties of this type for details.
/// </summary>
/// <returns>
/// A <see langword="bool"/> value indicating if all property values are default for this
/// <see cref="GenerateEnumExtensionMethodsAttribute"/> instance.
/// </returns>
/// <remarks>
/// Default values that will result in a <see langword="true"/> return value are:<br/>
/// <see cref="FastIsDefined"/> &amp;&amp; !<see cref="FastHasFlags"/> &amp;&amp; <see cref="ClassName"/>
/// <see langword="is"/> <see langword="null"/> &amp;&amp; <see cref="ClassNamespace"/> <see langword="is"/>
/// <see langword="null"/>
/// </remarks>
public override bool IsDefaultAttribute ()
{
return FastIsDefined
&& !FastHasFlags
&& ClassName is null
&& ClassNamespace is null;
}
}

View File

@@ -0,0 +1,110 @@
// ReSharper disable RedundantUsingDirective
using System;
using JetBrains.Annotations;
using Terminal.Gui.Analyzers.Internal.Compatibility;
namespace Terminal.Gui.Analyzers.Internal.Attributes;
/// <summary>
/// Designates an enum member for inclusion in generation of bitwise combinations with other members decorated with
/// this attribute which have the same <see cref="GroupTag"/> value.<br/>
/// </summary>
/// <remarks>
/// <para>
/// This attribute is only considered for enum types with the <see cref="GenerateEnumExtensionMethodsAttribute"/>.
/// </para>
/// </remarks>
[AttributeUsage (AttributeTargets.Enum)]
[UsedImplicitly]
public sealed class GenerateEnumMemberCombinationsAttribute : System.Attribute
{
private const byte MaximumPopCountLimit = 14;
private uint _mask;
private uint _maskPopCount;
private byte _popCountLimit = 8;
/// <inheritdoc cref="CombinationGroupingAttribute.GroupTag" />
public string GroupTag { get; set; }
/// <summary>
/// The mask for the group defined in <see cref="GroupTag"/>
/// </summary>
public uint Mask
{
get => _mask;
set
{
#if NET8_0_OR_GREATER
_maskPopCount = uint.PopCount (value);
#else
_maskPopCount = value.GetPopCount ();
#endif
PopCountLimitExceeded = _maskPopCount > PopCountLimit;
MaximumPopCountLimitExceeded = _maskPopCount > MaximumPopCountLimit;
if (PopCountLimitExceeded || MaximumPopCountLimitExceeded)
{
return;
}
_mask = value;
}
}
/// <summary>
/// The maximum number of bits allowed to be set to 1 in <see cref="Mask"/>.
/// </summary>
/// <remarks>
/// <para>
/// Default: 8 (256 possible combinations)
/// </para>
/// <para>
/// Increasing this value is not recommended!<br/>
/// Decreasing this value is pointless unless you want to limit maximum possible generated combinations even
/// further.
/// </para>
/// <para>
/// If the result of <see cref="NumericExtensions.GetPopCount(uint)"/>(<see cref="Mask"/>) exceeds 2 ^
/// <see cref="PopCountLimit"/>, no
/// combinations will be generated for the members which otherwise would have been included by <see cref="Mask"/>.
/// Values exceeding the actual population count of <see cref="Mask"/> have no effect.
/// </para>
/// <para>
/// This option is set to a sane default of 8, but also has a hard-coded limit of 14 (16384 combinations), as a
/// protection against generation of extremely large files.
/// </para>
/// <para>
/// CAUTION: The maximum number of possible combinations possible is equal to 1 &lt;&lt;
/// <see cref="NumericExtensions.GetPopCount(uint)"/>(<see cref="Mask"/>).
/// See <see cref="MaximumPopCountLimit"/> for hard-coded limit,
/// </para>
/// </remarks>
public byte PopCountLimit
{
get => _popCountLimit;
set
{
#if NET8_0_OR_GREATER
_maskPopCount = uint.PopCount (_mask);
#else
_maskPopCount = _mask.GetPopCount ();
#endif
PopCountLimitExceeded = _maskPopCount > value;
MaximumPopCountLimitExceeded = _maskPopCount > MaximumPopCountLimit;
if (PopCountLimitExceeded || MaximumPopCountLimitExceeded)
{
return;
}
_mask = value;
_popCountLimit = value;
}
}
[UsedImplicitly]
internal bool MaximumPopCountLimitExceeded { get; private set; }
[UsedImplicitly]
internal bool PopCountLimitExceeded { get; private set; }
}

View File

@@ -0,0 +1,14 @@
// ReSharper disable All
using System;
namespace Terminal.Gui.Analyzers.Internal.Attributes;
/// <summary>
/// Interface to simplify general enumeration of constructed generic types for
/// <see cref="ExtensionsForEnumTypeAttribute{TEnum}"/>
/// </summary>
internal interface IExtensionsForEnumTypeAttributes
{
Type EnumType { get; }
}

View File

@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;
/// <summary>
/// Indicates that compiler support for a particular feature is required for the location where this attribute is
/// applied.
/// </summary>
[AttributeUsage (AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class CompilerFeatureRequiredAttribute(string featureName) : Attribute
{
/// <summary>
/// The <see cref="FeatureName"/> used for the ref structs C# feature.
/// </summary>
public const string RefStructs = nameof (RefStructs);
/// <summary>
/// The <see cref="FeatureName"/> used for the required members C# feature.
/// </summary>
public const string RequiredMembers = nameof (RequiredMembers);
/// <summary>
/// The name of the compiler feature.
/// </summary>
public string FeatureName { get; } = featureName;
/// <summary>
/// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not
/// understand <see cref="FeatureName"/>.
/// </summary>
public bool IsOptional { get; init; }
}

View File

@@ -0,0 +1,11 @@
// ReSharper disable once CheckNamespace
namespace System.Numerics;
/// <summary>
/// Included for compatibility with .net7+, but has no members.
/// Thus it cannot be explicitly used in generator code.
/// Use it for static analysis only.
/// </summary>
/// <typeparam name="T">The left operand type.</typeparam>
/// <typeparam name="T1">The right operand type.</typeparam>
/// <typeparam name="T2">The return type.</typeparam>
internal interface IEqualityOperators<T, T1, T2>;

View File

@@ -0,0 +1,6 @@
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Field, Inherited = false)]
public sealed class IntrinsicAttribute : Attribute
{
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
// ReSharper disable CheckNamespace
namespace System.Runtime.CompilerServices;
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
/// <remarks>
/// Copied from .net source code, for support of init property accessors in netstandard2.0.
/// </remarks>
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
[EditorBrowsable (EditorBrowsableState.Never)]
public static class IsExternalInit;

View File

@@ -0,0 +1,208 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//
// This file is further modified from the original, for this project,
// to comply with project style.
// No changes are made which affect compatibility with the same types from
// APIs later than netstandard2.0, nor will this file be included in compilations
// targeted at later APIs.
//
// Originally rom https://github.com/dotnet/runtime/blob/ef72b95937703e485fdbbb75f3251fedfd1a0ef9/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
// ReSharper disable CheckNamespace
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable UnusedType.Global
namespace System.Diagnostics.CodeAnalysis;
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class AllowNullAttribute : Attribute;
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class DisallowNullAttribute : Attribute;
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class MaybeNullAttribute : Attribute;
/// <summary>
/// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input
/// argument was not null when the call returns.
/// </summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class NotNullAttribute : Attribute;
/// <summary>
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding
/// type disallows it.
/// </summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Parameter)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
#pragma warning disable IDE0290 // Use primary constructor
public MaybeNullWhenAttribute (bool returnValue) { ReturnValue = returnValue; }
#pragma warning restore IDE0290 // Use primary constructor
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the
/// corresponding type allows it.
/// </summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Parameter)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
#pragma warning disable IDE0290 // Use primary constructor
public NotNullWhenAttribute (bool returnValue) { ReturnValue = returnValue; }
#pragma warning restore IDE0290 // Use primary constructor
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class NotNullIfNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with the associated parameter name.</summary>
/// <param name="parameterName">
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
/// </param>
#pragma warning disable IDE0290 // Use primary constructor
public NotNullIfNotNullAttribute (string parameterName) { ParameterName = parameterName; }
#pragma warning restore IDE0290 // Use primary constructor
/// <summary>Gets the associated parameter name.</summary>
public string ParameterName { get; }
}
/// <summary>Applied to a method that will never return under any circumstance.</summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Method, Inherited = false)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class DoesNotReturnAttribute : Attribute;
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Parameter)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class DoesNotReturnIfAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified parameter value.</summary>
/// <param name="parameterValue">
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument
/// to
/// the associated parameter matches this value.
/// </param>
#pragma warning disable IDE0290 // Use primary constructor
public DoesNotReturnIfAttribute (bool parameterValue) { ParameterValue = parameterValue; }
#pragma warning restore IDE0290 // Use primary constructor
/// <summary>Gets the condition parameter value.</summary>
public bool ParameterValue { get; }
}
/// <summary>
/// Specifies that the method or property will ensure that the listed field and property members have not-null
/// values.
/// </summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class MemberNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with a field or property member.</summary>
/// <param name="member">
/// The field or property member that is promised to be not-null.
/// </param>
public MemberNotNullAttribute (string member) { Members = [member]; }
/// <summary>Initializes the attribute with the list of field and property members.</summary>
/// <param name="members">
/// The list of field and property members that are promised to be not-null.
/// </param>
public MemberNotNullAttribute (params string [] members) { Members = members; }
/// <summary>Gets field or property member names.</summary>
public string [] Members { get; }
}
/// <summary>
/// Specifies that the method or property will ensure that the listed field and property members have not-null values
/// when returning with the specified return value condition.
/// </summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
internal sealed class MemberNotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
/// <param name="member">
/// The field or property member that is promised to be not-null.
/// </param>
public MemberNotNullWhenAttribute (bool returnValue, string member)
{
ReturnValue = returnValue;
Members = [member];
}
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
/// <param name="members">
/// The list of field and property members that are promised to be not-null.
/// </param>
public MemberNotNullWhenAttribute (bool returnValue, params string [] members)
{
ReturnValue = returnValue;
Members = members;
}
/// <summary>Gets field or property member names.</summary>
public string [] Members { get; }
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}

View File

@@ -0,0 +1,43 @@
// ReSharper disable once CheckNamespace
namespace Terminal.Gui.Analyzers.Internal.Compatibility;
/// <summary>
/// Extension methods for <see langword="int"/> and <see langword="uint"/> types.
/// </summary>
/// <remarks>
/// This is mostly just for backward compatibility with netstandard2.0.
/// </remarks>
public static class NumericExtensions
{
/// <summary>
/// Gets the population count (number of bits set to 1) of this 32-bit value.
/// </summary>
/// <param name="value">The value to get the population count of.</param>
/// <remarks>
/// The algorithm is the well-known SWAR (SIMD Within A Register) method for population count.<br/>
/// Included for hardware- and runtime- agnostic support for the equivalent of the x86 popcnt instruction, since
/// System.Numerics.Intrinsics isn't available in netstandard2.0.<br/>
/// It performs the operation simultaneously on 4 bytes at a time, rather than the naive method of testing all 32 bits
/// individually.<br/>
/// Most compilers can recognize this and turn it into a single platform-specific instruction, when available.
/// </remarks>
/// <returns>
/// An unsigned 32-bit integer value containing the population count of <paramref name="value"/>.
/// </returns>
[MethodImpl (MethodImplOptions.AggressiveInlining)]
public static uint GetPopCount (this uint value)
{
unchecked
{
value -= (value >> 1) & 0x55555555;
value = (value & 0x33333333) + ((value >> 2) & 0x33333333);
value = (value + (value >> 4)) & 0x0F0F0F0F;
return (value * 0x01010101) >> 24;
}
}
/// <inheritdoc cref="GetPopCount(uint)"/>
[MethodImpl (MethodImplOptions.AggressiveInlining)]
public static uint GetPopCount (this int value) { return GetPopCount (Unsafe.As<int, uint> (ref value)); }
}

View File

@@ -0,0 +1,12 @@
// ReSharper disable CheckNamespace
// ReSharper disable ConditionalAnnotation
using JetBrains.Annotations;
namespace System.Runtime.CompilerServices;
/// <summary>Polyfill to enable netstandard2.0 assembly to use the required keyword.</summary>
/// <remarks>Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file.</remarks>
[AttributeUsage (AttributeTargets.Property)]
[UsedImplicitly]
public sealed class RequiredMemberAttribute : Attribute;

View File

@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// ReSharper disable once CheckNamespace
namespace System.Diagnostics.CodeAnalysis;
/// <summary>
/// Specifies that this constructor sets all required members for the current type, and callers
/// do not need to set any required members themselves.
/// </summary>
[AttributeUsage (AttributeTargets.Constructor)]
public sealed class SetsRequiredMembersAttribute : Attribute;

View File

@@ -0,0 +1,14 @@
namespace System.Runtime.CompilerServices;
[AttributeUsage (
AttributeTargets.Class
| AttributeTargets.Constructor
| AttributeTargets.Event
| AttributeTargets.Interface
| AttributeTargets.Method
| AttributeTargets.Module
| AttributeTargets.Property
| AttributeTargets.Struct,
Inherited = false)]
internal sealed class SkipLocalsInitAttribute : Attribute;

View File

@@ -0,0 +1,202 @@
// ReSharper disable MemberCanBePrivate.Global
using System;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Terminal.Gui.Analyzers.Internal.Attributes;
namespace Terminal.Gui.Analyzers.Internal.Constants;
/// <summary>String constants for frequently-used boilerplate.</summary>
/// <remarks>These are for performance, instead of using Roslyn to build it all during execution of analyzers.</remarks>
internal static class Strings
{
internal const string AnalyzersAttributesNamespace = $"{InternalAnalyzersNamespace}.Attributes";
internal const string AssemblyExtendedEnumTypeAttributeFullName = $"{AnalyzersAttributesNamespace}.{nameof (AssemblyExtendedEnumTypeAttribute)}";
internal const string DefaultTypeNameSuffix = "Extensions";
internal const string FallbackClassNamespace = $"{TerminalGuiRootNamespace}";
internal const string InternalAnalyzersNamespace = $"{AnalyzersRootNamespace}.Internal";
internal const string TerminalGuiRootNamespace = "Terminal.Gui";
private const string AnalyzersRootNamespace = $"{TerminalGuiRootNamespace}.Analyzers";
private const string NetStandard20CompatibilityNamespace = $"{InternalAnalyzersNamespace}.Compatibility";
/// <summary>
/// Names of dotnet namespaces and types. Included as compile-time constants to avoid unnecessary work for the Roslyn
/// source generators.
/// </summary>
/// <remarks>Implemented as nested static types because XmlDoc doesn't work on namespaces.</remarks>
internal static class DotnetNames
{
/// <summary>Fully-qualified attribute type names. Specific applications (uses) are in <see cref="Applications"/>.</summary>
internal static class Attributes
{
/// <inheritdoc cref="CompilerGeneratedAttribute"/>
internal const string CompilerGenerated = $"{Namespaces.System_Runtime_CompilerServices}.{nameof (CompilerGeneratedAttribute)}";
/// <inheritdoc cref="DebuggerNonUserCodeAttribute"/>
internal const string DebuggerNonUserCode = $"{Namespaces.System_Diagnostics}.{nameof (DebuggerNonUserCodeAttribute)}";
/// <inheritdoc cref="ExcludeFromCodeCoverageAttribute"/>
internal const string ExcludeFromCodeCoverage = $"{Namespaces.System_Diagnostics_CodeAnalysis}.{nameof (ExcludeFromCodeCoverageAttribute)}";
internal const string Flags = $"{Namespaces.SystemNS}.{nameof (FlagsAttribute)}";
internal const string GeneratedCode = $"{Namespaces.System_CodeDom_Compiler}.{nameof (GeneratedCodeAttribute)}";
/// <inheritdoc cref="MethodImplOptions.AggressiveInlining"/>
/// <remarks>Use of this attribute should be carefully evaluated.</remarks>
internal const string MethodImpl = $"{Namespaces.System_Runtime_CompilerServices}.{nameof (MethodImplAttribute)}";
/// <summary>Attributes formatted for use in code, including square brackets.</summary>
internal static class Applications
{
// ReSharper disable MemberHidesStaticFromOuterClass
internal const string Flags = $"[{Attributes.Flags}]";
/// <inheritdoc cref="System.CodeDom.Compiler.GeneratedCodeAttribute"/>
internal const string GeneratedCode = $"""[{Attributes.GeneratedCode}("{InternalAnalyzersNamespace}","1.0")]""";
/// <inheritdoc cref="MethodImplOptions.AggressiveInlining"/>
/// <remarks>Use of this attribute should be carefully evaluated.</remarks>
internal const string AggressiveInlining = $"[{MethodImpl}({Types.MethodImplOptions}.{nameof (MethodImplOptions.AggressiveInlining)})]";
/// <inheritdoc cref="DebuggerNonUserCodeAttribute"/>
internal const string DebuggerNonUserCode = $"[{Attributes.DebuggerNonUserCode}]";
/// <inheritdoc cref="CompilerGeneratedAttribute"/>
internal const string CompilerGenerated = $"[{Attributes.CompilerGenerated}]";
/// <inheritdoc cref="ExcludeFromCodeCoverageAttribute"/>
internal const string ExcludeFromCodeCoverage = $"[{Attributes.ExcludeFromCodeCoverage}]";
// ReSharper restore MemberHidesStaticFromOuterClass
}
}
/// <summary>Names of dotnet namespaces.</summary>
internal static class Namespaces
{
internal const string SystemNS = nameof (System);
internal const string System_CodeDom = $"{SystemNS}.{nameof (System.CodeDom)}";
internal const string System_CodeDom_Compiler = $"{System_CodeDom}.{nameof (System.CodeDom.Compiler)}";
internal const string System_ComponentModel = $"{SystemNS}.{nameof (System.ComponentModel)}";
internal const string System_Diagnostics = $"{SystemNS}.{nameof (System.Diagnostics)}";
internal const string System_Diagnostics_CodeAnalysis = $"{System_Diagnostics}.{nameof (System.Diagnostics.CodeAnalysis)}";
internal const string System_Numerics = $"{SystemNS}.{nameof (System.Numerics)}";
internal const string System_Runtime = $"{SystemNS}.{nameof (System.Runtime)}";
internal const string System_Runtime_CompilerServices = $"{System_Runtime}.{nameof (System.Runtime.CompilerServices)}";
}
internal static class Types
{
internal const string Attribute = $"{Namespaces.SystemNS}.{nameof (System.Attribute)}";
internal const string AttributeTargets = $"{Namespaces.SystemNS}.{nameof (System.AttributeTargets)}";
internal const string AttributeUsageAttribute = $"{Namespaces.SystemNS}.{nameof (System.AttributeUsageAttribute)}";
internal const string MethodImplOptions =
$"{Namespaces.System_Runtime_CompilerServices}.{nameof (System.Runtime.CompilerServices.MethodImplOptions)}";
}
}
internal static class Templates
{
internal const string AutoGeneratedCommentBlock = $"""
//------------------------------------------------------------------------------
// <auto-generated>
// This file and the code it contains was generated by a source generator in
// the {InternalAnalyzersNamespace} library.
//
// Modifications to this file are not supported and will be lost when
// source generation is triggered, either implicitly or explicitly.
// </auto-generated>
//------------------------------------------------------------------------------
""";
/// <summary>
/// A set of explicit type aliases to work around Terminal.Gui having name collisions with types like
/// <see cref="System.Attribute"/>.
/// </summary>
internal const string DotnetExplicitTypeAliasUsingDirectives = $"""
using Attribute = {DotnetNames.Types.Attribute};
using AttributeUsageAttribute = {DotnetNames.Types.AttributeUsageAttribute};
using GeneratedCode = {DotnetNames.Attributes.GeneratedCode};
""";
/// <summary>Using directives for common namespaces in generated code.</summary>
internal const string DotnetNamespaceUsingDirectives = $"""
using {DotnetNames.Namespaces.SystemNS};
using {DotnetNames.Namespaces.System_CodeDom};
using {DotnetNames.Namespaces.System_CodeDom_Compiler};
using {DotnetNames.Namespaces.System_ComponentModel};
using {DotnetNames.Namespaces.System_Numerics};
using {DotnetNames.Namespaces.System_Runtime};
using {DotnetNames.Namespaces.System_Runtime_CompilerServices};
""";
/// <summary>
/// A set of empty namespaces that MAY be referenced in generated code, especially in using statements,
/// which are always included to avoid additional complexity due to conditional compilation.
/// </summary>
internal const string DummyNamespaceDeclarations = $$"""
// These are dummy declarations to avoid complexity with conditional compilation.
#pragma warning disable IDE0079 // Remove unnecessary suppression
#pragma warning disable RCS1259 // Remove empty syntax
namespace {{TerminalGuiRootNamespace}} { }
namespace {{AnalyzersRootNamespace}} { }
namespace {{InternalAnalyzersNamespace}} { }
namespace {{NetStandard20CompatibilityNamespace}} { }
namespace {{AnalyzersAttributesNamespace}} { }
#pragma warning restore RCS1259 // Remove empty syntax
#pragma warning restore IDE0079 // Remove unnecessary suppression
""";
internal const string StandardHeader = $"""
{AutoGeneratedCommentBlock}
// ReSharper disable RedundantUsingDirective
// ReSharper disable once RedundantNullableDirective
{NullableContextDirective}
{StandardUsingDirectivesText}
""";
/// <summary>
/// Standard set of using directives for generated extension method class files.
/// Not all are always needed, but all are included so we don't have to worry about it.
/// </summary>
internal const string StandardUsingDirectivesText = $"""
{DotnetNamespaceUsingDirectives}
{DotnetExplicitTypeAliasUsingDirectives}
using {TerminalGuiRootNamespace};
using {AnalyzersRootNamespace};
using {InternalAnalyzersNamespace};
using {AnalyzersAttributesNamespace};
using {NetStandard20CompatibilityNamespace};
""";
internal const string AttributesForGeneratedInterfaces = $"""
{DotnetNames.Attributes.Applications.GeneratedCode}
{DotnetNames.Attributes.Applications.CompilerGenerated}
""";
internal const string AttributesForGeneratedTypes = $"""
{DotnetNames.Attributes.Applications.GeneratedCode}
{DotnetNames.Attributes.Applications.CompilerGenerated}
{DotnetNames.Attributes.Applications.DebuggerNonUserCode}
{DotnetNames.Attributes.Applications.ExcludeFromCodeCoverage}
""";
/// <summary>
/// Preprocessor directive to enable nullability context for generated code.<br/>
/// This should always be emitted, as it applies only to generated code.<br/>
/// As such, generated code MUST be properly annotated.
/// </summary>
internal const string NullableContextDirective = "#nullable enable";
}
}

View File

@@ -0,0 +1,235 @@
using System;
using System.CodeDom.Compiler;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using Microsoft.CodeAnalysis.Text;
using Terminal.Gui.Analyzers.Internal.Constants;
namespace Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
/// <summary>
/// The class responsible for turning an <see cref="EnumExtensionMethodsGenerationInfo"/>
/// into actual C# code.
/// </summary>
/// <remarks>Try to use this type as infrequently as possible.</remarks>
/// <param name="metadata">
/// A reference to an <see cref="IGeneratedTypeMetadata{TSelf}"/> which will be used
/// to generate the extension class code. The object will not be validated,
/// so it is critical that it be correct and remain unchanged while in use
/// by an instance of this class. Behavior if those rules are not followed
/// is undefined.
/// </param>
[SuppressMessage ("CodeQuality", "IDE0079", Justification = "Suppressions here are intentional and the warnings they disable are just noise.")]
internal sealed class CodeWriter (in EnumExtensionMethodsGenerationInfo metadata) : IStandardCSharpCodeGenerator<EnumExtensionMethodsGenerationInfo>
{
// Using the null suppression operator here because this will always be
// initialized to non-null before a reference to it is returned.
private SourceText _sourceText = null!;
/// <inheritdoc/>
public EnumExtensionMethodsGenerationInfo Metadata
{
[MethodImpl (MethodImplOptions.AggressiveInlining)]
[return: NotNull]
get;
[param: DisallowNull]
set;
} = metadata;
/// <inheritdoc/>
public ref readonly SourceText GenerateSourceText (Encoding? encoding = null)
{
encoding ??= Encoding.UTF8;
_sourceText = SourceText.From (GetFullSourceText (), encoding);
return ref _sourceText;
}
/// <summary>
/// Gets the using directive for the namespace containing the enum,
/// if different from the extension class namespace, or an empty string, if they are the same.
/// </summary>
private string EnumNamespaceUsingDirective => Metadata.TargetTypeNamespace != Metadata.GeneratedTypeNamespace
// ReSharper disable once HeapView.ObjectAllocation
? $"using {Metadata.TargetTypeNamespace};"
: string.Empty;
private string EnumTypeKeyword => Metadata.EnumBackingTypeCode switch
{
TypeCode.Int32 => "int",
TypeCode.UInt32 => "uint",
_ => string.Empty
};
/// <summary>Gets the class declaration line.</summary>
private string ExtensionClassDeclarationLine => $"public static partial class {Metadata.GeneratedTypeName}";
// ReSharper disable once HeapView.ObjectAllocation
/// <summary>Gets the XmlDoc for the extension class declaration.</summary>
private string ExtensionClassDeclarationXmlDoc =>
$"/// <summary>Extension methods for the <see cref=\"{Metadata.TargetTypeFullName}\"/> <see langword=\"enum\" /> type.</summary>";
// ReSharper disable once HeapView.ObjectAllocation
/// <summary>Gets the extension class file-scoped namespace directive.</summary>
private string ExtensionClassNamespaceDirective => $"namespace {Metadata.GeneratedTypeNamespace};";
/// <summary>
/// An attribute to decorate the extension class with for easy mapping back to the target enum type, for reflection and
/// analysis.
/// </summary>
private string ExtensionsForTypeAttributeLine => $"[ExtensionsForEnumType<{Metadata.TargetTypeFullName}>]";
/// <summary>
/// Creates the code for the FastHasFlags method.
/// </summary>
/// <remarks>
/// Since the generator already only writes code for enums backed by <see langword="int"/> and <see langword="uint"/>,
/// this method is safe, as we'll always be using a DWORD.
/// </remarks>
/// <param name="w">An instance of an <see cref="IndentedTextWriter"/> to write to.</param>
private void GetFastHasFlagsMethods (IndentedTextWriter w)
{
// The version taking the same enum type as the check value.
w.WriteLine (
$"/// <summary>Determines if the specified flags are set in the current value of this <see cref=\"{Metadata.TargetTypeFullName}\" />.</summary>");
w.WriteLine ("/// <remarks>NO VALIDATION IS PERFORMED!</remarks>");
w.WriteLine (
$"/// <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>");
w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining);
w.Push (
$"{Metadata.Accessibility.ToCSharpString ()} static bool FastHasFlags (this {Metadata.TargetTypeFullName} e, {Metadata.TargetTypeFullName} checkFlags)");
w.WriteLine ($"ref uint enumCurrentValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref e);");
w.WriteLine ($"ref uint checkFlagsValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref checkFlags);");
w.WriteLine ("return (enumCurrentValueRef & checkFlagsValueRef) == checkFlagsValueRef;");
w.Pop ();
// The version taking the underlying type of the enum as the check value.
w.WriteLine (
$"/// <summary>Determines if the specified mask bits are set in the current value of this <see cref=\"{Metadata.TargetTypeFullName}\" />.</summary>");
w.WriteLine (
$"/// <param name=\"e\">The <see cref=\"{Metadata.TargetTypeFullName}\" /> value to check against the <paramref name=\"mask\" /> value.</param>");
w.WriteLine ("/// <param name=\"mask\">A mask to apply to the current value.</param>");
w.WriteLine (
$"/// <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>");
w.WriteLine ("/// <remarks>NO VALIDATION IS PERFORMED!</remarks>");
w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining);
w.Push (
$"{Metadata.Accessibility.ToCSharpString ()} static bool FastHasFlags (this {Metadata.TargetTypeFullName} e, {EnumTypeKeyword} mask)");
w.WriteLine ($"ref {EnumTypeKeyword} enumCurrentValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},{EnumTypeKeyword}> (ref e);");
w.WriteLine ("return (enumCurrentValueRef & mask) == mask;");
w.Pop ();
}
/// <summary>
/// Creates the code for the FastIsDefined method.
/// </summary>
[SuppressMessage ("ReSharper", "SwitchStatementHandlesSomeKnownEnumValuesWithDefault", Justification = "Only need to handle int and uint.")]
[SuppressMessage ("ReSharper", "SwitchStatementMissingSomeEnumCasesNoDefault", Justification = "Only need to handle int and uint.")]
private void GetFastIsDefinedMethod (IndentedTextWriter w)
{
w.WriteLine (
$"/// <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>");
w.WriteLine (
"/// <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>");
w.Push (
$"{Metadata.Accessibility.ToCSharpString ()} static bool FastIsDefined (this {Metadata.TargetTypeFullName} e, {EnumTypeKeyword} value)");
w.Push ("return value switch");
switch (Metadata.EnumBackingTypeCode)
{
case TypeCode.Int32:
foreach (int definedValue in Metadata.IntMembers)
{
w.WriteLine ($"{definedValue:D} => true,");
}
break;
case TypeCode.UInt32:
foreach (uint definedValue in Metadata.UIntMembers)
{
w.WriteLine ($"{definedValue:D} => true,");
}
break;
}
w.WriteLine ("_ => false");
w.Pop ("};");
w.Pop ();
}
private string GetFullSourceText ()
{
StringBuilder sb = new (
$"""
{Strings.Templates.StandardHeader}
[assembly: {Strings.AssemblyExtendedEnumTypeAttributeFullName} (typeof({Metadata.TargetTypeFullName}), typeof({Metadata.GeneratedTypeFullName}))]
{EnumNamespaceUsingDirective}
{ExtensionClassNamespaceDirective}
{ExtensionClassDeclarationXmlDoc}
{Strings.Templates.AttributesForGeneratedTypes}
{ExtensionsForTypeAttributeLine}
{ExtensionClassDeclarationLine}
""",
4096);
using IndentedTextWriter w = new (new StringWriter (sb));
w.Push ();
GetNamedValuesToInt32Method (w);
GetNamedValuesToUInt32Method (w);
if (Metadata.GenerateFastIsDefined)
{
GetFastIsDefinedMethod (w);
}
if (Metadata.GenerateFastHasFlags)
{
GetFastHasFlagsMethods (w);
}
w.Pop ();
w.Flush ();
return sb.ToString ();
}
[MethodImpl (MethodImplOptions.AggressiveInlining)]
private void GetNamedValuesToInt32Method (IndentedTextWriter w)
{
w.WriteLine (
$"/// <summary>Directly converts this <see cref=\"{Metadata.TargetTypeFullName}\" /> value to an <see langword=\"int\" /> value with the same binary representation.</summary>");
w.WriteLine ("/// <remarks>NO VALIDATION IS PERFORMED!</remarks>");
w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining);
w.Push ($"{Metadata.Accessibility.ToCSharpString ()} static int AsInt32 (this {Metadata.TargetTypeFullName} e)");
w.WriteLine ($"return Unsafe.As<{Metadata.TargetTypeFullName},int> (ref e);");
w.Pop ();
}
[MethodImpl (MethodImplOptions.AggressiveInlining)]
private void GetNamedValuesToUInt32Method (IndentedTextWriter w)
{
w.WriteLine (
$"/// <summary>Directly converts this <see cref=\"{Metadata.TargetTypeFullName}\" /> value to a <see langword=\"uint\" /> value with the same binary representation.</summary>");
w.WriteLine ("/// <remarks>NO VALIDATION IS PERFORMED!</remarks>");
w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining);
w.Push ($"{Metadata.Accessibility.ToCSharpString ()} static uint AsUInt32 (this {Metadata.TargetTypeFullName} e)");
w.WriteLine ($"return Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref e);");
w.Pop ();
}
}

View File

@@ -0,0 +1,457 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Terminal.Gui.Analyzers.Internal.Attributes;
using Terminal.Gui.Analyzers.Internal.Constants;
namespace Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
/// <summary>
/// Type containing the information necessary to generate code according to the declared attribute values,
/// as well as the actual code to create the corresponding source code text, to be used in the
/// source generator pipeline.
/// </summary>
/// <remarks>
/// Minimal validation is performed by this type.<br/>
/// Errors in analyzed source code will result in generation failure or broken output.<br/>
/// This type is not intended for use outside of Terminal.Gui library development.
/// </remarks>
internal sealed record EnumExtensionMethodsGenerationInfo : IGeneratedTypeMetadata<EnumExtensionMethodsGenerationInfo>,
IEqualityOperators<EnumExtensionMethodsGenerationInfo, EnumExtensionMethodsGenerationInfo, bool>
{
private const int ExplicitFastHasFlagsMask = 0b1000;
private const int ExplicitFastIsDefinedMask = 0b1_0000;
private const int ExplicitIncludeInterfaceMask = 0b10_0000;
private const int ExplicitNameMask = 0b10;
private const int ExplicitNamespaceMask = 0b1;
private const int ExplicitPartialMask = 0b100;
private const string GeneratorAttributeFullyQualifiedName = $"{GeneratorAttributeNamespace}.{GeneratorAttributeName}";
private const string GeneratorAttributeName = nameof (GenerateEnumExtensionMethodsAttribute);
private const string GeneratorAttributeNamespace = Constants.Strings.AnalyzersAttributesNamespace;
/// <summary>
/// Type containing the information necessary to generate code according to the declared attribute values,
/// as well as the actual code to create the corresponding source code text, to be used in the
/// source generator pipeline.
/// </summary>
/// <param name="enumNamespace">The fully-qualified namespace of the enum type, without assembly name.</param>
/// <param name="enumTypeName">
/// The name of the enum type, as would be given by <see langword="nameof"/> on the enum's type
/// declaration.
/// </param>
/// <param name="typeNamespace">
/// The fully-qualified namespace in which to place the generated code, without assembly name. If omitted or explicitly
/// null, uses the value provided in <paramref name="enumNamespace"/>.
/// </param>
/// <param name="typeName">
/// The name of the generated class. If omitted or explicitly null, appends "Extensions" to the value of
/// <paramref name="enumTypeName"/>.
/// </param>
/// <param name="enumBackingTypeCode">The backing type of the enum. Defaults to <see cref="int"/>.</param>
/// <param name="generateFastHasFlags">
/// Whether to generate a fast HasFlag alternative. (Default: true) Ignored if the enum does not also have
/// <see cref="FlagsAttribute"/>.
/// </param>
/// <param name="generateFastIsDefined">Whether to generate a fast IsDefined alternative. (Default: true)</param>
/// <remarks>
/// Minimal validation is performed by this type.<br/>
/// Errors in analyzed source code will result in generation failure or broken output.<br/>
/// This type is not intended for use outside of Terminal.Gui library development.
/// </remarks>
public EnumExtensionMethodsGenerationInfo (
string enumNamespace,
string enumTypeName,
string? typeNamespace = null,
string? typeName = null,
TypeCode enumBackingTypeCode = TypeCode.Int32,
bool generateFastHasFlags = true,
bool generateFastIsDefined = true
) : this (enumNamespace, enumTypeName, enumBackingTypeCode)
{
GeneratedTypeNamespace = typeNamespace ?? enumNamespace;
GeneratedTypeName = typeName ?? string.Concat (enumTypeName, Strings.DefaultTypeNameSuffix);
GenerateFastHasFlags = generateFastHasFlags;
GenerateFastIsDefined = generateFastIsDefined;
}
public EnumExtensionMethodsGenerationInfo (string enumNamespace, string enumTypeName, TypeCode enumBackingType)
{
// Interning these since they're rather unlikely to change.
string enumInternedNamespace = string.Intern (enumNamespace);
string enumInternedName = string.Intern (enumTypeName);
TargetTypeNamespace = enumInternedNamespace;
TargetTypeName = enumInternedName;
EnumBackingTypeCode = enumBackingType;
}
[AccessedThroughProperty (nameof (EnumBackingTypeCode))]
private TypeCode _enumBackingTypeCode;
[AccessedThroughProperty (nameof (GeneratedTypeName))]
private string? _generatedTypeName;
[AccessedThroughProperty (nameof (GeneratedTypeNamespace))]
private string? _generatedTypeNamespace;
private BitVector32 _discoveredProperties = new (0);
/// <summary>The name of the extension class.</summary>
public string? GeneratedTypeName
{
get => _generatedTypeName ?? string.Concat (TargetTypeName, Strings.DefaultTypeNameSuffix);
set => _generatedTypeName = value ?? string.Concat (TargetTypeName, Strings.DefaultTypeNameSuffix);
}
/// <summary>The namespace for the extension class.</summary>
/// <remarks>
/// Value is not validated by the set accessor.<br/>
/// Get accessor will never return null and is thus marked [NotNull] for static analysis, even though the property is
/// declared as a nullable <see langword="string?"/>.<br/>If the backing field for this property is null, the get
/// accessor will return <see cref="TargetTypeNamespace"/> instead.
/// </remarks>
public string? GeneratedTypeNamespace
{
get => _generatedTypeNamespace ?? TargetTypeNamespace;
set => _generatedTypeNamespace = value ?? TargetTypeNamespace;
}
/// <inheritdoc/>
public string TargetTypeFullName => string.Concat (TargetTypeNamespace, ".", TargetTypeName);
/// <inheritdoc/>
public Accessibility Accessibility
{
get;
[UsedImplicitly]
internal set;
} = Accessibility.Public;
/// <inheritdoc/>
public TypeKind TypeKind => TypeKind.Class;
/// <inheritdoc/>
public bool IsRecord => false;
/// <inheritdoc/>
public bool IsClass => true;
/// <inheritdoc/>
public bool IsStruct => false;
/// <inheritdoc/>
public bool IsByRefLike => false;
/// <inheritdoc/>
public bool IsSealed => false;
/// <inheritdoc/>
public bool IsAbstract => false;
/// <inheritdoc/>
public bool IsEnum => false;
/// <inheritdoc/>
public bool IsStatic => true;
/// <inheritdoc/>
public bool IncludeInterface { get; private set; }
public string GeneratedTypeFullName => $"{GeneratedTypeNamespace}.{GeneratedTypeName}";
/// <summary>Whether to generate the extension class as partial (Default: true)</summary>
public bool IsPartial => true;
/// <summary>The fully-qualified namespace of the source enum type.</summary>
public string TargetTypeNamespace
{
get;
[UsedImplicitly]
set;
}
/// <summary>The UNQUALIFIED name of the source enum type.</summary>
public string TargetTypeName
{
get;
[UsedImplicitly]
set;
}
/// <summary>
/// The backing type for the enum.
/// </summary>
/// <remarks>For simplicity and formality, only System.Int32 and System.UInt32 are supported at this time.</remarks>
public TypeCode EnumBackingTypeCode
{
get => _enumBackingTypeCode;
set
{
if (value is not TypeCode.Int32 and not TypeCode.UInt32)
{
throw new NotSupportedException ("Only System.Int32 and System.UInt32 are supported at this time.");
}
_enumBackingTypeCode = value;
}
}
/// <summary>
/// Whether a fast alternative to the built-in Enum.HasFlag method will be generated (Default: false)
/// </summary>
public bool GenerateFastHasFlags { [UsedImplicitly] get; set; }
/// <summary>Whether a switch-based IsDefined replacement will be generated (Default: true)</summary>
public bool GenerateFastIsDefined { [UsedImplicitly]get; set; } = true;
internal ImmutableHashSet<int>? IntMembers;
internal ImmutableHashSet<uint>? UIntMembers;
/// <summary>
/// Fully-qualified name of the extension class
/// </summary>
internal string FullyQualifiedClassName => $"{GeneratedTypeNamespace}.{GeneratedTypeName}";
/// <summary>
/// Whether a Flags was found on the enum type.
/// </summary>
internal bool HasFlagsAttribute {[UsedImplicitly] get; set; }
private static readonly SymbolDisplayFormat FullyQualifiedSymbolDisplayFormatWithoutGlobal =
SymbolDisplayFormat.FullyQualifiedFormat
.WithGlobalNamespaceStyle (
SymbolDisplayGlobalNamespaceStyle.Omitted);
internal bool TryConfigure (INamedTypeSymbol enumSymbol, CancellationToken cancellationToken)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken);
cts.Token.ThrowIfCancellationRequested ();
ImmutableArray<AttributeData> attributes = enumSymbol.GetAttributes ();
// This is theoretically impossible, but guarding just in case and canceling if it does happen.
if (attributes.Length == 0)
{
cts.Cancel (true);
return false;
}
// Check all attributes provided for anything interesting.
// Attributes can be in any order, so just check them all and adjust at the end if necessary.
// Note that we do not perform as strict validation on actual usage of the attribute, at this stage,
// because the analyzer should have already thrown errors for invalid uses like global namespace
// or unsupported enum underlying types.
foreach (AttributeData attr in attributes)
{
cts.Token.ThrowIfCancellationRequested ();
string? attributeFullyQualifiedName = attr.AttributeClass?.ToDisplayString (FullyQualifiedSymbolDisplayFormatWithoutGlobal);
// Skip if null or not possibly an attribute we care about
if (attributeFullyQualifiedName is null or not { Length: >= 5 })
{
continue;
}
switch (attributeFullyQualifiedName)
{
// For Flags enums
case Strings.DotnetNames.Attributes.Flags:
{
HasFlagsAttribute = true;
}
continue;
// For the attribute that started this whole thing
case GeneratorAttributeFullyQualifiedName:
{
// If we can't successfully complete this method,
// something is wrong enough that we may as well just stop now.
if (!TryConfigure (attr, cts.Token))
{
if (cts.Token.CanBeCanceled)
{
cts.Cancel ();
}
return false;
}
}
continue;
}
}
// Now get the members, if we know we'll need them.
if (GenerateFastIsDefined || GenerateFastHasFlags)
{
if (EnumBackingTypeCode == TypeCode.Int32)
{
PopulateIntMembersHashSet (enumSymbol);
}
else if (EnumBackingTypeCode == TypeCode.UInt32)
{
PopulateUIntMembersHashSet (enumSymbol);
}
}
return true;
}
private void PopulateIntMembersHashSet (INamedTypeSymbol enumSymbol)
{
ImmutableArray<ISymbol> enumMembers = enumSymbol.GetMembers ();
IEnumerable<IFieldSymbol> fieldSymbols = enumMembers.OfType<IFieldSymbol> ();
IntMembers = fieldSymbols.Select (static m => m.HasConstantValue ? (int)m.ConstantValue : 0).ToImmutableHashSet ();
}
private void PopulateUIntMembersHashSet (INamedTypeSymbol enumSymbol)
{
UIntMembers = enumSymbol.GetMembers ().OfType<IFieldSymbol> ().Select (static m => (uint)m.ConstantValue).ToImmutableHashSet ();
}
private bool HasExplicitFastHasFlags
{
[UsedImplicitly]get => _discoveredProperties [ExplicitFastHasFlagsMask];
set => _discoveredProperties [ExplicitFastHasFlagsMask] = value;
}
private bool HasExplicitFastIsDefined
{
[UsedImplicitly]get => _discoveredProperties [ExplicitFastIsDefinedMask];
set => _discoveredProperties [ExplicitFastIsDefinedMask] = value;
}
private bool HasExplicitIncludeInterface
{
[UsedImplicitly]get => _discoveredProperties [ExplicitIncludeInterfaceMask];
set => _discoveredProperties [ExplicitIncludeInterfaceMask] = value;
}
private bool HasExplicitPartial
{
[UsedImplicitly]get => _discoveredProperties [ExplicitPartialMask];
set => _discoveredProperties [ExplicitPartialMask] = value;
}
private bool HasExplicitTypeName
{
get => _discoveredProperties [ExplicitNameMask];
set => _discoveredProperties [ExplicitNameMask] = value;
}
private bool HasExplicitTypeNamespace
{
get => _discoveredProperties [ExplicitNamespaceMask];
set => _discoveredProperties [ExplicitNamespaceMask] = value;
}
[MemberNotNullWhen (true, nameof (_generatedTypeName), nameof (_generatedTypeNamespace))]
private bool TryConfigure (AttributeData attr, CancellationToken cancellationToken)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken);
cts.Token.ThrowIfCancellationRequested ();
if (attr is not { NamedArguments.Length: > 0 })
{
// Just a naked attribute, so configure with appropriate defaults.
GeneratedTypeNamespace = TargetTypeNamespace;
GeneratedTypeName = string.Concat (TargetTypeName, Strings.DefaultTypeNameSuffix);
return true;
}
cts.Token.ThrowIfCancellationRequested ();
foreach (KeyValuePair<string, TypedConstant> kvp in attr.NamedArguments)
{
string? propName = kvp.Key;
TypedConstant propValue = kvp.Value;
cts.Token.ThrowIfCancellationRequested ();
// For every property name and value pair, set associated metadata
// property, if understood.
switch (propName, propValue)
{
// Null or empty string doesn't make sense, so skip if it happens.
case (null, _):
case ("", _):
continue;
// ClassName is specified, not explicitly null, and at least 1 character long.
case (AttributeProperties.TypeNamePropertyName, { IsNull: false, Value: string { Length: > 1 } classNameProvidedValue }):
if (string.IsNullOrWhiteSpace (classNameProvidedValue))
{
return false;
}
GeneratedTypeName = classNameProvidedValue;
HasExplicitTypeName = true;
continue;
// Class namespace is specified, not explicitly null, and at least 1 character long.
case (AttributeProperties.TypeNamespacePropertyName, { IsNull: false, Value: string { Length: > 1 } classNamespaceProvidedValue }):
if (string.IsNullOrWhiteSpace (classNamespaceProvidedValue))
{
return false;
}
GeneratedTypeNamespace = classNamespaceProvidedValue;
HasExplicitTypeNamespace = true;
continue;
// FastHasFlags is specified
case (AttributeProperties.FastHasFlagsPropertyName, { IsNull: false } fastHasFlagsConstant):
GenerateFastHasFlags = fastHasFlagsConstant.Value is true;
HasExplicitFastHasFlags = true;
continue;
// FastIsDefined is specified
case (AttributeProperties.FastIsDefinedPropertyName, { IsNull: false } fastIsDefinedConstant):
GenerateFastIsDefined = fastIsDefinedConstant.Value is true;
HasExplicitFastIsDefined = true;
continue;
}
}
// The rest is simple enough it's not really worth worrying about cancellation, so don't bother from here on...
// Configure anything that wasn't specified that doesn't have an implicitly safe default
if (!HasExplicitTypeName || _generatedTypeName is null)
{
_generatedTypeName = string.Concat (TargetTypeName, Strings.DefaultTypeNameSuffix);
}
if (!HasExplicitTypeNamespace || _generatedTypeNamespace is null)
{
_generatedTypeNamespace = TargetTypeNamespace;
}
if (!HasFlagsAttribute)
{
GenerateFastHasFlags = false;
}
return true;
}
private static class AttributeProperties
{
internal const string FastHasFlagsPropertyName = nameof (GenerateEnumExtensionMethodsAttribute.FastHasFlags);
internal const string FastIsDefinedPropertyName = nameof (GenerateEnumExtensionMethodsAttribute.FastIsDefined);
internal const string TypeNamePropertyName = nameof (GenerateEnumExtensionMethodsAttribute.ClassName);
internal const string TypeNamespacePropertyName = nameof (GenerateEnumExtensionMethodsAttribute.ClassNamespace);
}
}

View File

@@ -0,0 +1,452 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Terminal.Gui.Analyzers.Internal.Attributes;
using Terminal.Gui.Analyzers.Internal.Constants;
namespace Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
/// <summary>
/// Incremental code generator for enums decorated with <see cref="GenerateEnumExtensionMethodsAttribute"/>.
/// </summary>
[SuppressMessage ("CodeQuality", "IDE0079", Justification = "Suppressions here are intentional and the warnings they disable are just noise.")]
[Generator (LanguageNames.CSharp)]
public sealed class EnumExtensionMethodsIncrementalGenerator : IIncrementalGenerator
{
private const string ExtensionsForEnumTypeAttributeFullyQualifiedName = $"{Strings.AnalyzersAttributesNamespace}.{ExtensionsForEnumTypeAttributeName}";
private const string ExtensionsForEnumTypeAttributeName = "ExtensionsForEnumTypeAttribute";
private const string GeneratorAttributeFullyQualifiedName = $"{Strings.AnalyzersAttributesNamespace}.{GeneratorAttributeName}";
private const string GeneratorAttributeName = nameof (GenerateEnumExtensionMethodsAttribute);
/// <summary>Fully-qualified symbol name format without the "global::" prefix.</summary>
private static readonly SymbolDisplayFormat FullyQualifiedSymbolDisplayFormatWithoutGlobal =
SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle (SymbolDisplayGlobalNamespaceStyle.Omitted);
/// <inheritdoc/>
/// <remarks>
/// <para>
/// Basically, this method is called once by the compiler, and is responsible for wiring up
/// everything important about how source generation works.
/// </para>
/// <para>
/// See in-line comments for specifics of what's going on.
/// </para>
/// <para>
/// Note that <paramref name="context"/> is everything in the compilation,
/// except for code generated by this generator or generators which have not yet executed.<br/>
/// The methods registered to perform generation get called on-demand by the host (the IDE,
/// compiler, etc), sometimes as often as every single keystroke.
/// </para>
/// </remarks>
public void Initialize (IncrementalGeneratorInitializationContext context)
{
// Write out namespaces that may be used later. Harmless to declare them now and will avoid
// additional processing and potential omissions later on.
context.RegisterPostInitializationOutput (GenerateDummyNamespaces);
// This executes the delegate given to it immediately after Roslyn gets all set up.
//
// As written, this will result in the GenerateEnumExtensionMethodsAttribute code
// being added to the environment, so that it can be used without having to actually
// be declared explicitly in the target project.
// This is important, as it guarantees the type will exist and also guarantees it is
// defined exactly as the generator expects it to be defined.
context.RegisterPostInitializationOutput (GenerateAttributeSources);
// Next up, we define our pipeline.
// To do so, we create one or more IncrementalValuesProvider<T> objects, each of which
// defines on stage of analysis or generation as needed.
//
// Critically, these must be as fast and efficient as reasonably possible because,
// once the pipeline is registered, this stuff can get called A LOT.
//
// Note that declaring these doesn't really do much of anything unless they are given to the
// RegisterSourceOutput method at the end of this method.
//
// The delegates are not actually evaluated right here. That is triggered by changes being
// made to the source code.
// This provider grabs attributes that pass our filter and then creates lightweight
// metadata objects to be used in the final code generation step.
// It also preemptively removes any nulls from the collection before handing things off
// to the code generation logic.
IncrementalValuesProvider<EnumExtensionMethodsGenerationInfo?> enumGenerationInfos =
context
.SyntaxProvider
// This method is a highly-optimized (and highly-recommended) filter on the incoming
// code elements that only bothers to present code that is annotated with the specified
// attribute, by its fully-qualified name, as a string, which is the first parameter.
//
// Two delegates are passed to it, in the second and third parameters.
//
// The second parameter is a filter predicate taking each SyntaxNode that passes the
// name filter above, and then refines that result.
//
// It is critical that the filter predicate be as simple and fast as possible, as it
// will be called a ton, triggered by keystrokes or anything else that modifies code
// in or even related to (in either direction) the pre-filtered code.
// It should collect metadata only and not actually generate any code.
// It must return a boolean indicating whether the supplied SyntaxNode should be
// given to the transform delegate at all.
//
// The third parameter is the "transform" delegate.
// That one only runs when code is changed that passed both the attribute name filter
// and the filter predicate in the second parameter.
// It will be called for everything that passes both of those, so it can still happen
// a lot, but should at least be pretty close.
// In our case, it should be 100% accurate, since we're using OUR attribute, which can
// only be applied to enum types in the first place.
//
// That delegate is responsible for creating some sort of lightweight data structure
// which can later be used to generate the actual source code for output.
//
// THIS DELEGATE DOES NOT GENERATE CODE!
// However, it does need to return instances of the metadata class in use that are either
// null or complete enough to generate meaningful code from, later on.
//
// We then filter out any that were null with the .Where call at the end, so that we don't
// know or care about them when it's time to generate code.
//
// While the syntax of that .Where call is the same as LINQ, that is actually a
// highly-optimized implementation specifically for this use.
.ForAttributeWithMetadataName (
GeneratorAttributeFullyQualifiedName,
IsPotentiallyInterestingDeclaration,
GatherMetadataForCodeGeneration
)
.WithTrackingName ("CollectEnumMetadata")
.Where (static eInfo => eInfo is { });
// Finally, we wire up any IncrementalValuesProvider<T> instances above to the appropriate
// delegate that takes the SourceProductionContext that is current at run-time and an instance of
// our metadata type and takes appropriate action.
// Typically that means generating code from that metadata and adding it to the compilation via
// the received context object.
//
// As with everything else , the delegate will be invoked once for each item that passed
// all of the filters above, so we get to write that method from the perspective of a single
// enum type declaration.
context.RegisterSourceOutput (enumGenerationInfos, GenerateSourceFromGenerationInfo);
}
private static EnumExtensionMethodsGenerationInfo? GatherMetadataForCodeGeneration (
GeneratorAttributeSyntaxContext context,
CancellationToken cancellationToken
)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken);
cancellationToken.ThrowIfCancellationRequested ();
// If it's not an enum symbol, we don't care.
// EnumUnderlyingType is null for non-enums, so this validates it's an enum declaration.
if (context.TargetSymbol is not INamedTypeSymbol { EnumUnderlyingType: { } } namedSymbol)
{
return null;
}
INamespaceSymbol? enumNamespaceSymbol = namedSymbol.ContainingNamespace;
if (enumNamespaceSymbol is null or { IsGlobalNamespace: true })
{
// Explicitly choosing not to support enums in the global namespace.
// The corresponding analyzer will report this.
return null;
}
string enumName = namedSymbol.Name;
string enumNamespace = enumNamespaceSymbol.ToDisplayString (FullyQualifiedSymbolDisplayFormatWithoutGlobal);
TypeCode enumTypeCode = namedSymbol.EnumUnderlyingType.Name switch
{
"UInt32" => TypeCode.UInt32,
"Int32" => TypeCode.Int32,
_ => TypeCode.Empty
};
EnumExtensionMethodsGenerationInfo info = new (
enumNamespace,
enumName,
enumTypeCode
);
if (!info.TryConfigure (namedSymbol, cts.Token))
{
cts.Cancel ();
cts.Token.ThrowIfCancellationRequested ();
}
return info;
}
private static void GenerateAttributeSources (IncrementalGeneratorPostInitializationContext postInitializationContext)
{
postInitializationContext
.AddSource (
$"{nameof (IExtensionsForEnumTypeAttributes)}.g.cs",
SourceText.From (
$$"""
// ReSharper disable All
{{Strings.Templates.AutoGeneratedCommentBlock}}
using System;
namespace {{Strings.AnalyzersAttributesNamespace}};
/// <summary>
/// Interface to simplify general enumeration of constructed generic types for
/// <see cref="ExtensionsForEnumTypeAttribute{TEnum}"/>
/// </summary>
{{Strings.Templates.AttributesForGeneratedInterfaces}}
public interface IExtensionsForEnumTypeAttributes
{
System.Type EnumType { get; }
}
""",
Encoding.UTF8));
postInitializationContext
.AddSource (
$"{nameof (AssemblyExtendedEnumTypeAttribute)}.g.cs",
SourceText.From (
$$"""
// ReSharper disable All
#nullable enable
{{Strings.Templates.AutoGeneratedCommentBlock}}
namespace {{Strings.AnalyzersAttributesNamespace}};
/// <summary>Assembly attribute declaring a known pairing of an <see langword="enum" /> type to an extension class.</summary>
/// <remarks>This attribute should only be written by internal source generators for Terminal.Gui. No other usage of any kind is supported.</remarks>
{{Strings.Templates.AttributesForGeneratedTypes}}
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class {{nameof(AssemblyExtendedEnumTypeAttribute)}} : System.Attribute
{
/// <summary>Creates a new instance of <see cref="AssemblyExtendedEnumTypeAttribute" /> from the provided parameters.</summary>
/// <param name="enumType">The <see cref="System.Type" /> of an <see langword="enum" /> decorated with a <see cref="GenerateEnumExtensionMethodsAttribute" />.</param>
/// <param name="extensionClass">The <see cref="System.Type" /> of the <see langword="class" /> decorated with an <see cref="ExtensionsForEnumTypeAttribute{TEnum}" /> referring to the same type as <paramref name="enumType" />.</param>
public AssemblyExtendedEnumTypeAttribute (System.Type enumType, System.Type extensionClass)
{
EnumType = enumType;
ExtensionClass = extensionClass;
}
/// <summary>An <see langword="enum" /> type that has been extended by Terminal.Gui source generators.</summary>
public System.Type EnumType { get; init; }
/// <summary>A class containing extension methods for <see cref="EnumType"/>.</summary>
public System.Type ExtensionClass { get; init; }
/// <inheritdoc />
public override string ToString () => $"{EnumType.Name},{ExtensionClass.Name}";
}
""",
Encoding.UTF8));
postInitializationContext
.AddSource (
$"{GeneratorAttributeFullyQualifiedName}.g.cs",
SourceText.From (
$$"""
{{Strings.Templates.StandardHeader}}
namespace {{Strings.AnalyzersAttributesNamespace}};
/// <summary>
/// Used to enable source generation of a common set of extension methods for enum types.
/// </summary>
{{Strings.Templates.AttributesForGeneratedTypes}}
[System.AttributeUsageAttribute (System.AttributeTargets.Enum)]
public sealed class {{GeneratorAttributeName}} : Attribute
{
/// <summary>
/// The name of the generated static class.
/// </summary>
/// <remarks>
/// If unspecified, null, empty, or only whitespace, defaults to the name of the enum plus "Extensions".<br/>
/// No other validation is performed, so illegal values will simply result in compiler errors.
/// <para>
/// Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
/// </para>
/// </remarks>
public string? ClassName { get; set; }
/// <summary>
/// The namespace in which to place the generated static class containing the extension methods.
/// </summary>
/// <remarks>
/// If unspecified, null, empty, or only whitespace, defaults to the namespace of the enum.<br/>
/// No other validation is performed, so illegal values will simply result in compiler errors.
/// <para>
/// Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
/// </para>
/// </remarks>
public string? ClassNamespace { get; set; }
/// <summary>
/// Whether to generate a fast, zero-allocation, non-boxing, and reflection-free alternative to the built-in
/// <see cref="Enum.HasFlag"/> method.
/// </summary>
/// <remarks>
/// <para>
/// Default: false
/// </para>
/// <para>
/// If the enum is not decorated with <see cref="Flags"/>, this option has no effect.
/// </para>
/// <para>
/// If multiple members have the same value, the first member with that value will be used and subsequent members
/// with the same value will be skipped.
/// </para>
/// <para>
/// Overloads taking the enum type itself as well as the underlying type of the enum will be generated, enabling
/// avoidance of implicit or explicit cast overhead.
/// </para>
/// <para>
/// Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
/// </para>
/// </remarks>
public bool FastHasFlags { get; set; }
/// <summary>
/// Whether to generate a fast, zero-allocation, and reflection-free alternative to the built-in
/// <see cref="Enum.IsDefined"/> method,
/// using a switch expression as a hard-coded reverse mapping of numeric values to explicitly-named members.
/// </summary>
/// <remarks>
/// <para>
/// Default: true
/// </para>
/// <para>
/// If multiple members have the same value, the first member with that value will be used and subsequent members
/// with the same value will be skipped.
/// </para>
/// <para>
/// As with <see cref="Enum.IsDefined"/> the source generator only considers explicitly-named members.<br/>
/// Generation of values which represent valid bitwise combinations of members of enums decorated with
/// <see cref="Flags"/> is not affected by this property.
/// </para>
/// </remarks>
public bool FastIsDefined { get; init; } = true;
/// <summary>
/// Gets a <see langword="bool"/> value indicating if this <see cref="GenerateEnumExtensionMethodsAttribute"/> instance
/// contains default values only. See <see href="#remarks">remarks</see> of this method or documentation on properties of this type for details.
/// </summary>
/// <returns>
/// A <see langword="bool"/> value indicating if all property values are default for this
/// <see cref="GenerateEnumExtensionMethodsAttribute"/> instance.
/// </returns>
/// <remarks>
/// Default values that will result in a <see langword="true"/> return value are:<br/>
/// <see cref="FastIsDefined"/> &amp;&amp; !<see cref="FastHasFlags"/> &amp;&amp; <see cref="ClassName"/>
/// <see langword="is"/> <see langword="null"/> &amp;&amp; <see cref="ClassNamespace"/> <see langword="is"/>
/// <see langword="null"/>
/// </remarks>
public override bool IsDefaultAttribute ()
{
return FastIsDefined
&& !FastHasFlags
&& ClassName is null
&& ClassNamespace is null;
}
}
""",
Encoding.UTF8));
postInitializationContext
.AddSource (
$"{ExtensionsForEnumTypeAttributeFullyQualifiedName}.g.cs",
SourceText.From (
$$"""
// ReSharper disable RedundantNameQualifier
// ReSharper disable RedundantNullableDirective
// ReSharper disable UnusedType.Global
{{Strings.Templates.AutoGeneratedCommentBlock}}
#nullable enable
namespace {{Strings.AnalyzersAttributesNamespace}};
/// <summary>
/// Attribute written by the source generator for enum extension classes, for easier analysis and reflection.
/// </summary>
/// <remarks>
/// Properties are just convenient shortcuts to properties of <typeparamref name="TEnum"/>.
/// </remarks>
{{Strings.Templates.AttributesForGeneratedTypes}}
[System.AttributeUsageAttribute (System.AttributeTargets.Class | System.AttributeTargets.Interface)]
public sealed class {{ExtensionsForEnumTypeAttributeName}}<TEnum>: System.Attribute, IExtensionsForEnumTypeAttributes where TEnum : struct, Enum
{
/// <summary>
/// The namespace-qualified name of <typeparamref name="TEnum"/>.
/// </summary>
public string EnumFullName => EnumType.FullName!;
/// <summary>
/// The unqualified name of <typeparamref name="TEnum"/>.
/// </summary>
public string EnumName => EnumType.Name;
/// <summary>
/// The namespace containing <typeparamref name="TEnum"/>.
/// </summary>
public string EnumNamespace => EnumType.Namespace!;
/// <summary>
/// The <see cref="Type"/> given by <see langword="typeof"/>(<typeparamref name="TEnum"/>).
/// </summary>
public Type EnumType => typeof (TEnum);
}
""",
Encoding.UTF8));
}
[SuppressMessage ("Roslynator", "RCS1267", Justification = "Intentionally used so that Spans are used.")]
private static void GenerateDummyNamespaces (IncrementalGeneratorPostInitializationContext postInitializeContext)
{
postInitializeContext.AddSource (
string.Concat (Strings.InternalAnalyzersNamespace, "Namespaces.g.cs"),
SourceText.From (Strings.Templates.DummyNamespaceDeclarations, Encoding.UTF8));
}
private static void GenerateSourceFromGenerationInfo (SourceProductionContext context, EnumExtensionMethodsGenerationInfo? enumInfo)
{
// Just in case we still made it this far with a null...
if (enumInfo is not { })
{
return;
}
CodeWriter writer = new (enumInfo);
context.AddSource ($"{enumInfo.FullyQualifiedClassName}.g.cs", writer.GenerateSourceText ());
}
/// <summary>
/// Returns true if <paramref name="syntaxNode"/> is an EnumDeclarationSyntax
/// whose parent is a NamespaceDeclarationSyntax, FileScopedNamespaceDeclarationSyntax, or a
/// (Class|Struct)DeclarationSyntax.<br/>
/// Additional filtering is performed in later stages.
/// </summary>
private static bool IsPotentiallyInterestingDeclaration (SyntaxNode syntaxNode, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested ();
return syntaxNode is
{
RawKind: 8858, //(int)SyntaxKind.EnumDeclaration,
Parent.RawKind: 8845 //(int)SyntaxKind.FileScopedNamespaceDeclaration
or 8842 //(int)SyntaxKind.NamespaceDeclaration
or 8855 //(int)SyntaxKind.ClassDeclaration
or 8856 //(int)SyntaxKind.StructDeclaration
or 9068 //(int)SyntaxKind.RecordStructDeclaration
or 9063 //(int)SyntaxKind.RecordDeclaration
};
}
}

View File

@@ -0,0 +1,130 @@
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Terminal.Gui.Analyzers.Internal.Attributes;
using Terminal.Gui.Analyzers.Internal.Constants;
namespace Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
/// <summary>
/// Implementation of <see cref="IIncrementalGenerator"/> for types decorated with <see cref="GenerateEnumMemberCombinationsAttribute"/>.
/// </summary>
[Generator]
internal sealed class EnumMemberCombinationsGenerator : IIncrementalGenerator
{
private const string AttributeCodeText = $$"""
{{Strings.Templates.StandardHeader}}
namespace {{Strings.AnalyzersAttributesNamespace}};
/// <summary>
/// Designates an enum member for inclusion in generation of bitwise combinations with other members decorated with
/// this attribute which have the same <see cref="{{nameof (GenerateEnumMemberCombinationsAttribute.GroupTag)}}"/> value.<br/>
/// </summary>
/// <remarks>
/// <para>
/// This attribute is only considered for enum types with the <see cref="{{nameof (GenerateEnumMemberCombinationsAttribute)}}"/>.
/// </para>
/// <para>
/// Masks with more than 8 bits set will
/// </para>
/// </remarks>
[AttributeUsageAttribute(AttributeTargets.Enum)]
internal sealed class {{nameof (GenerateEnumMemberCombinationsAttribute)}} : System.Attribute
{
public const byte MaximumPopCountLimit = 14;
private uint _mask;
private uint _maskPopCount;
private byte _popCountLimit = 8;
public required string GroupTag { get; set; }
public required uint Mask
{
get => _mask;
set
{
_maskPopCount = uint.PopCount(value);
PopCountLimitExceeded = _maskPopCount > PopCountLimit;
MaximumPopCountLimitExceeded = _maskPopCount > MaximumPopCountLimit;
if (PopCountLimitExceeded || MaximumPopCountLimitExceeded)
{
return;
}
_mask = value;
}
}
/// <summary>
/// The maximum number of bits allowed to be set to 1 in <see cref="{{nameof (GenerateEnumMemberCombinationsAttribute.Mask)}}"/>.
/// </summary>
/// <remarks>
/// <para>
/// Default: 8 (256 possible combinations)
/// </para>
/// <para>
/// Increasing this value is not recommended!<br/>
/// Decreasing this value is pointless unless you want to limit maximum possible generated combinations even
/// further.
/// </para>
/// <para>
/// If the result of <see cref="uint.PopCount"/>(<see cref="{{nameof (GenerateEnumMemberCombinationsAttribute.Mask)}}"/>) exceeds 2 ^ <see cref="PopCountLimit"/>, no
/// combinations will be generated for the members which otherwise would have been included by <see cref="{{nameof (GenerateEnumMemberCombinationsAttribute.Mask)}}"/>.
/// Values exceeding the actual population count of <see cref="{{nameof (GenerateEnumMemberCombinationsAttribute.Mask)}}"/> have no effect.
/// </para>
/// <para>
/// This option is set to a sane default of 8, but also has a hard-coded limit of 14 (16384 combinations), as a
/// protection against generation of extremely large files.
/// </para>
/// <para>
/// CAUTION: The maximum number of possible combinations possible is equal to 1 &lt;&lt;
/// <see cref="uint.PopCount"/>(<see cref="{{nameof (GenerateEnumMemberCombinationsAttribute.Mask)}}"/>).
/// See <see cref="MaximumPopCountLimit"/> for hard-coded limit,
/// </para>
/// </remarks>
public byte PopCountLimit
{
get => _popCountLimit;
set
{
_maskPopCount = uint.PopCount(_mask);
PopCountLimitExceeded = _maskPopCount > value;
MaximumPopCountLimitExceeded = _maskPopCount > MaximumPopCountLimit;
if (PopCountLimitExceeded || MaximumPopCountLimitExceeded)
{
return;
}
_mask = value;
_popCountLimit = value;
}
}
internal bool MaximumPopCountLimitExceeded { get; private set; }
internal bool PopCountLimitExceeded { get; private set; }
}
""";
private const string AttributeFullyQualifiedName = $"{Strings.AnalyzersAttributesNamespace}.{AttributeName}";
private const string AttributeName = "GenerateEnumMemberCombinationsAttribute";
/// <inheritdoc/>
public void Initialize (IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput (GenerateAttributeCode);
return;
static void GenerateAttributeCode (IncrementalGeneratorPostInitializationContext initContext)
{
#pragma warning disable IDE0061 // Use expression body for local function
initContext.AddSource ($"{AttributeFullyQualifiedName}.g.cs", SourceText.From (AttributeCodeText, Encoding.UTF8));
#pragma warning restore IDE0061 // Use expression body for local function
}
}
}

View File

@@ -0,0 +1,38 @@
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
namespace Terminal.Gui.Analyzers.Internal;
/// <summary>
/// Interface for all generators to use for their metadata classes.
/// </summary>
/// <typeparam name="TSelf">The type implementing this interface.</typeparam>
internal interface IGeneratedTypeMetadata<out TSelf> where TSelf : IGeneratedTypeMetadata<TSelf>
{
[UsedImplicitly]
string GeneratedTypeNamespace { get; }
[UsedImplicitly]
string? GeneratedTypeName { get; }
[UsedImplicitly]
string GeneratedTypeFullName { get; }
[UsedImplicitly]
string TargetTypeNamespace { get; }
[UsedImplicitly]
string TargetTypeName { get; }
string TargetTypeFullName { get; }
[UsedImplicitly]
Accessibility Accessibility { get; }
TypeKind TypeKind { get; }
bool IsRecord { get; }
bool IsClass { get; }
bool IsStruct { get; }
[UsedImplicitly]
bool IsPartial { get; }
bool IsByRefLike { get; }
bool IsSealed { get; }
bool IsAbstract { get; }
bool IsEnum { get; }
bool IsStatic { get; }
[UsedImplicitly]
bool IncludeInterface { get; }
}

View File

@@ -0,0 +1,28 @@
using System.Text;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis.Text;
namespace Terminal.Gui.Analyzers.Internal;
internal interface IStandardCSharpCodeGenerator<T> where T : IGeneratedTypeMetadata<T>
{
/// <summary>
/// Generates and returns the full source text corresponding to <see cref="Metadata"/>,
/// in the requested <paramref name="encoding"/> or <see cref="Encoding.UTF8"/> if not provided.
/// </summary>
/// <param name="encoding">
/// The <see cref="Encoding"/> of the generated source text or <see cref="Encoding.UTF8"/> if not
/// provided.
/// </param>
/// <returns></returns>
[UsedImplicitly]
[SkipLocalsInit]
ref readonly SourceText GenerateSourceText (Encoding? encoding = null);
/// <summary>
/// A type implementing <see cref="IGeneratedTypeMetadata{T}"/> which
/// will be used for source generation.
/// </summary>
[UsedImplicitly]
T Metadata { get; set; }
}

View File

@@ -0,0 +1,71 @@
using System.CodeDom.Compiler;
namespace Terminal.Gui.Analyzers.Internal;
/// <summary>
/// Just a simple set of extension methods to increment and decrement the indentation
/// level of an <see cref="IndentedTextWriter"/> via push and pop terms, and to avoid having
/// explicit values all over the place.
/// </summary>
public static class IndentedTextWriterExtensions
{
/// <summary>
/// Decrements <see cref="IndentedTextWriter.Indent"/> by 1, but only if it is greater than 0.
/// </summary>
/// <returns>
/// The resulting indentation level of the <see cref="IndentedTextWriter"/>.
/// </returns>
[MethodImpl (MethodImplOptions.AggressiveInlining)]
public static int Pop (this IndentedTextWriter w, string endScopeDelimiter = "}")
{
if (w.Indent > 0)
{
w.Indent--;
w.WriteLine (endScopeDelimiter);
}
return w.Indent;
}
/// <summary>
/// Decrements <see cref="IndentedTextWriter.Indent"/> by 1 and then writes a closing curly brace.
/// </summary>
[MethodImpl (MethodImplOptions.AggressiveInlining)]
public static void PopCurly (this IndentedTextWriter w, bool withSemicolon = false)
{
w.Indent--;
if (withSemicolon)
{
w.WriteLine ("};");
}
else
{
w.WriteLine ('}');
}
}
/// <summary>
/// Increments <see cref="IndentedTextWriter.Indent"/> by 1, with optional parameters to customize the scope push.
/// </summary>
/// <param name="w">An instance of an <see cref="IndentedTextWriter"/>.</param>
/// <param name="declaration">
/// The first line to be written before indenting and before the optional <paramref name="scopeDelimiter"/> line or
/// null if not needed.
/// </param>
/// <param name="scopeDelimiter">
/// An opening delimiter to write. Written before the indentation and after <paramref name="declaration"/> (if provided). Default is an opening curly brace.
/// </param>
/// <remarks>Calling with no parameters will write an opening curly brace and a line break at the current indentation and then increment.</remarks>
[MethodImpl (MethodImplOptions.AggressiveInlining)]
public static void Push (this IndentedTextWriter w, string? declaration = null, char scopeDelimiter = '{')
{
if (declaration is { Length: > 0 })
{
w.WriteLine (declaration);
}
w.WriteLine (scopeDelimiter);
w.Indent++;
}
}

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"InternalAnalyzers Debug": {
"commandName": "DebugRoslynComponent",
"targetProject": "..\\Terminal.Gui.Analyzers.Internal.Debugging\\Terminal.Gui.Analyzers.Internal.Debugging.csproj"
}
}
}

View File

@@ -0,0 +1,98 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--
Do not remove netstandard2.0 from the TargetFrameworks.
Visual Studio requires that Analyzers/Generators target netstandard2.0 to work properly.
Additional TFMs are for support of additional APIs and language features.
-->
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<OutputType>Library</OutputType>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<RootNamespace>Terminal.Gui.Analyzers.Internal</RootNamespace>
<ImplicitUsings>disable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<WarningLevel>7</WarningLevel>
<CharacterSet>UTF-8</CharacterSet>
<CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode>
<Deterministic>true</Deterministic>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<UTF8OutPut>true</UTF8OutPut>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<DefineConstants>$(DefineConstants);JETBRAINS_ANNOTATIONS;CONTRACTS_FULL;CODE_ANALYSIS</DefineConstants>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<IsRoslynComponent>true</IsRoslynComponent>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
<ApiCompatExcludeAttributesFile Include="ApiCompatExcludedAttributes.txt" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Compatibility/*.cs" />
</ItemGroup>
<Choose>
<When Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PropertyGroup>
<!-- Disabling some useless warnings caused by the netstandard2.0 target -->
<NoWarn>$(NoWarn);nullable;CA1067</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Include="Compatibility/CompilerFeatureRequiredAttribute.cs" />
<Compile Include="Compatibility/IsExternalInit.cs" />
<Compile Include="Compatibility/IEqualityOperators.cs" />
<Compile Include="Compatibility/NullableAttributes.cs" />
<Compile Include="Compatibility/RequiredMemberAttribute.cs" />
<Compile Include="Compatibility/SetsRequiredMembersAttribute.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" PrivateAssets="all" />
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" PrivateAssets="all" />
<PackageReference Include="System.Runtime.Numerics" Version="4.3.0" PrivateAssets="all" />
</ItemGroup>
</When>
<When Condition="'$(TargetFramework)' == 'net8.0'">
</When>
</Choose>
<ItemGroup>
<Compile Include="Compatibility/NumericExtensions.cs" />
<Compile Include="Compatibility/SkipLocalsInitAttribute.cs" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="Roslynator.Analyzers" Version="4.12.1" PrivateAssets="all">
<!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
</PackageReference>
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" Version="4.12.1" PrivateAssets="all">
<!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
</PackageReference>
<PackageReference Include="Roslynator.CSharp" Version="4.12.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<Using Include="System.Buffers" />
<Using Include="System.Collections.Specialized" />
<Using Include="System.Numerics" />
<Using Include="System.Runtime.CompilerServices" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp120</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/UsageCheckingInspectionLevel/@EntryValue">InternalsOnly</s:String>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=compatibility/@EntryIndexedValue">False</s:Boolean></wpf:ResourceDictionary>

6
Directory.build.props Normal file
View File

@@ -0,0 +1,6 @@
<Project>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" PrivateAssets="all" />
<PackageReference Include="JetBrains.ExternalAnnotations" Version="10.2.147" PrivateAssets="all" />
</ItemGroup>
</Project>

16
Scripts/COPYRIGHT Normal file
View File

@@ -0,0 +1,16 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The MIT License (MIT)
// Copyright © 2024 Brandon Thetford (@dodexahedron)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

46
Scripts/README.md Normal file
View File

@@ -0,0 +1,46 @@
## Development and Design-Time PowerShell Modules
This directory contains PowerShell modules for use when working with Terminal.sln
### Purpose
These modules will be modifed and extended as time goes on, whenever someone decides to add something to make life easier.
### Requirements
These modules are designed for **PowerShell Core, version 7.4 or higher**, on any platform, and must be run directly within a pwsh process.\
If you want to use them from within another application, such as PowerShell hosted inside VSCode, you must first run `pwsh` in that terminal.
As the primary development environment for Terminal.Gui is Visual Studio 2022+, some functionality may be limited, unavailable, or not work on platforms other than Windows.\
Most should still work on Linux, however.\
Functions which are platform-specific will be documented as such in their Get-Help documentation.
Specific requirements for each module can be found in the module manifests and will be automatically imported or, if unavailable, PowerShell will tell you what's missing.
### Usage
From a PowerShell 7.4 or higher prompt, navigate to your Terminal.Gui repository directory, and then into the Scripts directory (the same directory as this document).
#### Import Module and Configure Environment
Run the following command to import all Terminal.Gui.PowerShell.* modules:
```powershell
Import-Module ./Terminal.Gui.PowerShell.psd1
```
If the environment meets the requirements, the modules will now be loaded into the current powershell session and exported commands will be immediately available for use.
#### Getting Help
All exported functions and commandlets are provided with full PowerShell help annotations compatible with `Get-Help`.
See [The Get-Help documentation at Microsoft Learn]([https://](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/get-help?view=powershell-7.4)) for Get-Help information.
#### Cleaning Up/Resetting Environment
No environment changes made by the modules on import are persistent.
When you are finished using the modules, you can optionally unload the modules, which will also reset the configuration changes made on import, by simply exiting the PowerShell session (`exit`) or by running the following command:\
**NOTE DIFFERENT TEXT FROM IMPORT COMMAND!**
```powershell
Remove-Module Terminal.Gui.PowerShell
```
### LICENSE
MIT License
Original Author: Brandon Thetford (@dodexahedron)
See COPYRIGHT in this directory for license text.

View File

@@ -0,0 +1,105 @@
<#
.SYNOPSIS
Builds all analyzer projects in Debug and Release configurations.
.DESCRIPTION
Uses dotnet build to build all analyzer projects, with optional behavior changes via switch parameters.
.PARAMETER AutoClose
Automatically close running Visual Studio processes which have the Terminal.sln solution loaded, before taking any other actions.
.PARAMETER AutoLaunch
Automatically start a new Visual Studio process and load the solution after completion.
.PARAMETER Force
Carry out operations unconditionally and do not prompt for confirmation.
.PARAMETER NoClean
Do not delete the bin and obj folders before building the analyzers. Usually best not to use this, but can speed up the builds slightly.
.PARAMETER Quiet
Write less text output to the terminal.
.INPUTS
None
.OUTPUTS
None
#>
Function Build-Analyzers {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false, HelpMessage="Automatically close running Visual Studio processes which have the Terminal.sln solution loaded, before taking any other actions.")]
[switch]$AutoClose,
[Parameter(Mandatory=$false, HelpMessage="Automatically start a new Visual Studio process and load the solution after completion.")]
[switch]$AutoLaunch,
[Parameter(Mandatory=$false, HelpMessage="Carry out operations unconditionally and do not prompt for confirmation.")]
[switch]$Force,
[Parameter(Mandatory=$false, HelpMessage="Do not delete the bin and obj folders before building the analyzers.")]
[switch]$NoClean,
[Parameter(Mandatory=$false, HelpMessage="Write less text output to the terminal.")]
[switch]$Quiet
)
if($AutoClose) {
if(!$Quiet) {
Write-Host Closing Visual Studio processes
}
Close-Solution
}
if($Force){
$response = 'Y'
}
elseif(!$Force && $NoClean){
$response = ($r = Read-Host "Pre-build Terminal.Gui.InternalAnalyzers without removing old build artifacts? [Y/n]") ? $r : 'Y'
}
else{
$response = ($r = Read-Host "Delete bin and obj folders for Terminal.Gui and Terminal.Gui.InternalAnalyzers and pre-build Terminal.Gui.InternalAnalyzers? [Y/n]") ? $r : 'Y'
}
if (($response -ne 'Y')) {
Write-Host Took no action
return
}
New-Variable -Name solutionRoot -Visibility Public -Value (Resolve-Path ..)
Push-Location $solutionRoot
New-Variable -Name solutionFile -Visibility Public -Value (Resolve-Path ./Terminal.sln)
$mainProjectRoot = Resolve-Path ./Terminal.Gui
$mainProjectFile = Join-Path $mainProjectRoot Terminal.Gui.csproj
$analyzersRoot = Resolve-Path ./Analyzers
$internalAnalyzersProjectRoot = Join-Path $analyzersRoot Terminal.Gui.Analyzers.Internal
$internalAnalyzersProjectFile = Join-Path $internalAnalyzersProjectRoot Terminal.Gui.Analyzers.Internal.csproj
if(!$NoClean) {
if(!$Quiet) {
Write-Host Deleting bin and obj folders for Terminal.Gui
}
if(Test-Path $mainProjectRoot/bin) {
Remove-Item -Recurse -Force $mainProjectRoot/bin
Remove-Item -Recurse -Force $mainProjectRoot/obj
}
if(!$Quiet) {
Write-Host Deleting bin and obj folders for Terminal.Gui.InternalAnalyzers
}
if(Test-Path $internalAnalyzersProjectRoot/bin) {
Remove-Item -Recurse -Force $internalAnalyzersProjectRoot/bin
Remove-Item -Recurse -Force $internalAnalyzersProjectRoot/obj
}
}
if(!$Quiet) {
Write-Host Building analyzers in Debug configuration
}
dotnet build $internalAnalyzersProjectFile --no-incremental --nologo --force --configuration Debug
if(!$Quiet) {
Write-Host Building analyzers in Release configuration
}
dotnet build $internalAnalyzersProjectFile --no-incremental --nologo --force --configuration Release
if(!$AutoLaunch) {
Write-Host -ForegroundColor Green Finished. Restart Visual Studio for changes to take effect.
} else {
if(!$Quiet) {
Write-Host -ForegroundColor Green Finished. Re-loading Terminal.sln.
}
Open-Solution
}
return
}

View File

@@ -0,0 +1,131 @@
@{
# No root module because this is a manifest module.
RootModule = ''
# Version number of this module.
ModuleVersion = '1.0.0'
# Supported PSEditions
CompatiblePSEditions = @('Core')
# ID used to uniquely identify this module
GUID = 'c4a1de77-83fb-45a3-b1b5-18d275ef3601'
# Author of this module
Author = 'Brandon Thetford (GitHub @dodexahedron)'
# Company or vendor of this module
CompanyName = 'The Terminal.Gui Project'
# Copyright statement for this module
Copyright = 'Brandon Thetford (GitHub @dodexahedron), provided to the Terminal.Gui project and you under the MIT license'
# Description of the functionality provided by this module
Description = 'Build helper functions for Terminal.Gui.'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '7.4.0'
# Name of the PowerShell "host" subsystem (not system host name). Helps ensure that we know what to expect from the environment.
PowerShellHostName = 'ConsoleHost'
# Minimum version of the PowerShell host required by this module
PowerShellHostVersion = '7.4.0'
# Processor architecture (None, MSIL, X86, IA64, Amd64, Arm, or an empty string) required by this module. One value only.
# Set to AMD64 here because development on Terminal.Gui isn't really supported on anything else.
# Has nothing to do with runtime use of Terminal.Gui.
ProcessorArchitecture = 'Amd64'
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @(
@{
ModuleName='Microsoft.PowerShell.Utility'
ModuleVersion='7.0.0'
},
@{
ModuleName='Microsoft.PowerShell.Management'
ModuleVersion='7.0.0'
},
@{
ModuleName='PSReadLine'
ModuleVersion='2.3.4'
},
"./Terminal.Gui.PowerShell.Core.psd1"
)
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules.
NestedModules = @('./Terminal.Gui.PowerShell.Build.psm1')
# Functions to export from this module.
FunctionsToExport = @('Build-TerminalGui')
# Cmdlets to export from this module.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module.
AliasesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
LicenseUri = 'https://github.com/gui-cs/Terminal.Gui/tree/v2_develop/Scripts/COPYRIGHT'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/gui-cs/Terminal.Gui'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
ReleaseNotes = 'See change history and releases for Terminal.Gui on GitHub'
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}

View File

@@ -0,0 +1,32 @@
<#
.SYNOPSIS
Builds the Terminal.Gui library.
.DESCRIPTION
Builds the Terminal.Gui library.
Optional parameter sets are available to customize the build.
.PARAMETER versionBase
The base version for the Terminal.Gui library.
#>
Function Build-TerminalGui {
[CmdletBinding(SupportsShouldProcess, PositionalBinding=$false, DefaultParameterSetName="Basic", ConfirmImpact="Medium")]
[OutputType([bool],[PSObject])]
param(
[Parameter(Mandatory=$true)]
[Version]$versionBase,
[Parameter(Mandatory=$true, ParameterSetName="Custom")]
[switch]$Custom,
[Parameter(Mandatory=$false, ParameterSetName="Custom")]
[ValidateSet("Debug", "Release")]
[string]$slnBuildConfiguration = "Release",
[Parameter(Mandatory=$false, ParameterSetName="Custom")]
[ValidateSet("Any CPU", "x86"<#, "x64" #>)]
[string]$slnBuildPlatform = "Any CPU"
)
if(!$PSCmdlet.ShouldProcess("Building in $slnBuildConfiguration configuration for $slnBuildPlatform", "Terminal.Gui", "BUILDING")) {
return $null
}
Write-Host NOT IMPLEMENTED. No Action has been taken.
return $false
}

View File

@@ -0,0 +1,138 @@
#
# Module manifest for module 'Terminal.Gui.PowerShell'
#
# Generated by: Brandon Thetford (GitHub @dodexahedron)
#
# Generated on: 4/19/2024
#
@{
# No root module because this is a manifest module.
RootModule = ''
# Version number of this module.
ModuleVersion = '1.0.0'
# Supported PSEditions
CompatiblePSEditions = @('Core')
# ID used to uniquely identify this module
GUID = 'c661fb12-70ae-4a9e-a95c-786a7980681d'
# Author of this module
Author = 'Brandon Thetford (GitHub @dodexahedron)'
# Company or vendor of this module
CompanyName = 'The Terminal.Gui Project'
# Copyright statement for this module
Copyright = 'Brandon Thetford (GitHub @dodexahedron), provided to the Terminal.Gui project and you under the MIT license'
# Description of the functionality provided by this module
Description = 'Utilities for development-time operations on and management of components of Terminal.Gui code and other assets.'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '7.4.0'
# Name of the PowerShell "host" subsystem (not system host name). Helps ensure that we know what to expect from the environment.
PowerShellHostName = 'ConsoleHost'
# Minimum version of the PowerShell host required by this module
PowerShellHostVersion = '7.4.0'
# Processor architecture (None, MSIL, X86, IA64, Amd64, Arm, or an empty string) required by this module. One value only.
# Set to AMD64 here because development on Terminal.Gui isn't really supported on anything else.
# Has nothing to do with runtime use of Terminal.Gui.
ProcessorArchitecture = 'Amd64'
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @(
@{
ModuleName='Microsoft.PowerShell.Utility'
ModuleVersion='7.0.0'
},
@{
ModuleName='Microsoft.PowerShell.Management'
ModuleVersion='7.0.0'
},
@{
ModuleName='PSReadLine'
ModuleVersion='2.3.4'
}
)
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = @('./Terminal.Gui.PowerShell.Core.psm1')
# Functions to export from this module.
FunctionsToExport = @('Open-Solution','Close-Solution')
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
LicenseUri = 'https://github.com/gui-cs/Terminal.Gui/tree/v2_develop/Scripts/COPYRIGHT'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/gui-cs/Terminal.Gui'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
ReleaseNotes = 'See change history and releases for Terminal.Gui on GitHub'
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}

View File

@@ -0,0 +1,143 @@
<#
.SYNOPSIS
(Windows Only) Opens Visual Studio and loads Terminal.sln.
.DESCRIPTION
(Windows Only) Opens Visual Studio and loads Terminal.sln.
.PARAMETER SolutionFilePath
(Optional) If specified, the path to the solution file. Typically unnecessary to supply this parameter.
.INPUTS
None
.OUTPUTS
None
#>
Function Open-Solution {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false, HelpMessage="The path to the solution file to open.")]
[Uri]$SolutionFilePath = (Resolve-Path "../Terminal.sln")
)
if(!$IsWindows) {
[string]$warningMessage = "The Open-Solution cmdlet is only supported on Windows.`n`
Attempt to open file $SolutionFilePath with the system default handler?"
Write-Warning $warningMessage -WarningAction Inquire
}
Invoke-Item $SolutionFilePath
return
}
<#
.SYNOPSIS
(Windows Only) Closes Visual Studio processes with Terminal.sln loaded.
.DESCRIPTION
(Windows Only) Closes Visual Studio processes with Terminal.sln loaded by finding any VS processes launched with the solution file or with 'Terminal' in their main window titles.
.INPUTS
None
.OUTPUTS
None
#>
Function Close-Solution {
$vsProcesses = Get-Process -Name devenv | Where-Object { ($_.CommandLine -Match ".*Terminal\.sln.*" -or $_.MainWindowTitle -Match "Terminal.*") }
Stop-Process -InputObject $vsProcesses
Remove-Variable vsProcesses
}
<#
.SYNOPSIS
Sets up a standard environment for other Terminal.Gui.PowerShell scripts and modules.
.DESCRIPTION
Configures environment variables and global variables for other Terminal.Gui.PowerShell scripts to use.
Also modifies the prompt to indicate the session has been altered.
Reset changes by exiting the session or by calling Reset-PowerShellEnvironment or ./ResetEnvironment.ps1.
.PARAMETER Debug
Minimally supported for Write-Debug calls in this function only.
.NOTES
Mostly does not respect common parameters like WhatIf, Confirm, etc.
This is just meant to be called by other scripts.
Calling this manually is not supported.
#>
Function Set-PowerShellEnvironment {
[CmdletBinding()]
param()
# Set a custom prompt to indicate we're in our modified environment.
# Save the normal one first, though.
# And save it as ReadOnly and without the -Force parameter, so this will be skipped if run more than once in the same session without a reset.
New-Variable -Name NormalPrompt -Option ReadOnly -Scope Global -Value (Get-Item Function:prompt).ScriptBlock -ErrorAction SilentlyContinue
Set-Item Function:prompt { "TGPS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "; }
# Save existing PSModulePath for optional reset later.
# If it is already saved, do not overwrite, but continue anyway.
New-Variable -Name OriginalPSModulePath -Visibility Public -Option ReadOnly -Scope Global -Value ($Env:PSModulePath) -ErrorAction SilentlyContinue
Write-Debug -Message "`$OriginalPSModulePath is $OriginalPSModulePath" -Debug:$DebugPreference
# Get platform-specific path variable entry separator. Continue if it's already set.
New-Variable -Name PathVarSeparator -Visibility Public -Option ReadOnly -Scope Global -Value ";" -Description 'Separator character used in environment variables such as $Env:PSModulePath' -ErrorAction SilentlyContinue
if(!$IsWindows) {
$PathVarSeparator = ':'
}
Write-Debug -Message "`$PathVarSeparator is $PathVarSeparator" -Debug:$DebugPreference
# If Env:PSModulePath already has the current path, don't append it again.
if($Env:PSModulePath -notlike "*$((Resolve-Path .).Path)*") {
Write-Debug -Message "Appending $((Resolve-Path .).Path) to `$Env:PSModulePath" -Debug:$DebugPreference
$env:PSModulePath = Join-String -Separator $PathVarSeparator -InputObject @( $env:PSModulePath, (Resolve-Path .).Path )
}
Write-Debug -Message "`$Env:PSModulePath is $Env:PSModulePath" -Debug:$DebugPreference
}
<#
.SYNOPSIS
Resets changes made by ConfigureEnvironment.pst to the current PowerShell environment.
.DESCRIPTION
Optional function to undo changes to the current session made by ConfigureEnvironment.ps1.
Changes only affect the current session, so exiting will also "reset."
.PARAMETER Exit
Switch parameter that, if specified, exits the current PowerShell environment.
Does not bother doing any other operations, as none are necessary.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Reset-PowerShellEnvironment
To undo changes in the current session.
.EXAMPLE
Reset-PowerShellEnvironment -Exit
To exit the current session. Same as simply using the Exit command.
#>
Function Reset-PowerShellEnvironment {
[CmdletBinding(DefaultParameterSetName="Basic")]
param(
[Parameter(Mandatory=$false, ParameterSetName="Basic")]
[switch]$Exit
)
if($Exit) {
[Environment]::Exit(0)
}
if(Get-Variable -Name NormalPrompt -Scope Global -ErrorAction SilentlyContinue){
Set-Item Function:prompt $NormalPrompt
Remove-Variable -Name NormalPrompt -Scope Global -Force -ErrorAction SilentlyContinue
}
if(Get-Variable -Name OriginalPSModulePath -Scope Global -ErrorAction SilentlyContinue){
$Env:PSModulePath = $OriginalPSModulePath
Remove-Variable -Name OriginalPSModulePath -Scope Global -Force -ErrorAction SilentlyContinue
}
Remove-Variable -Name PathVarSeparator -Scope Global -Force -ErrorAction SilentlyContinue
}
# This ensures the environment is reset when unloading the module.
# Without this, function:prompt will be undefined.
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
Reset-PowerShellEnvironment
}
Set-PowerShellEnvironment

View File

@@ -0,0 +1,135 @@
#
# Module manifest for module 'Terminal.Gui.PowerShell.Git'
#
# Generated by: Brandon Thetford
#
# Generated on: 4/26/2024
#
@{
# Script module or binary module file associated with this manifest.
RootModule = ''
# Version number of this module.
ModuleVersion = '1.0.0'
# Supported PSEditions
CompatiblePSEditions = 'Core'
# ID used to uniquely identify this module
GUID = '33a6c4c9-c0a7-4c09-b171-1da0878f93ea'
# Author of this module
Author = 'Brandon Thetford (GitHub @dodexahedron)'
# Company or vendor of this module
CompanyName = 'The Terminal.Gui Project'
# Copyright statement for this module
Copyright = 'Brandon Thetford (GitHub @dodexahedron), provided to the Terminal.Gui project and you under the MIT license'
# Description of the functionality provided by this module
Description = 'Simple helper commands for common git operations.'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '7.4'
# Name of the PowerShell host required by this module
PowerShellHostName = 'ConsoleHost'
# Minimum version of the PowerShell host required by this module
PowerShellHostVersion = '7.4.0'
# Processor architecture (None, MSIL, X86, IA64, Amd64, Arm, or an empty string) required by this module. One value only.
# Set to AMD64 here because development on Terminal.Gui isn't really supported on anything else.
# Has nothing to do with runtime use of Terminal.Gui.
ProcessorArchitecture = 'AMD64'
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @(
@{
ModuleName='Microsoft.PowerShell.Utility'
ModuleVersion='7.0.0'
},
@{
ModuleName='Microsoft.PowerShell.Management'
ModuleVersion='7.0.0'
},
@{
ModuleName='PSReadLine'
ModuleVersion='2.3.4'
}
)
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = @()
# Modules to import as nested modules.
NestedModules = @("./Terminal.Gui.PowerShell.Git.psm1")
# Functions to export from this module.
FunctionsToExport = @('New-GitBranch')
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
DscResourcesToExport = @()
# List of all modules packaged with this module
ModuleList = @('./Terminal.Gui.PowerShell.Git.psm1')
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}

View File

@@ -0,0 +1,111 @@
<#
.SYNOPSIS
Creates a new branch with the specified name.
.DESCRIPTION
Creates a new branch with the specified name.
.PARAMETER Name
The name of the new branch.
Always required.
Must match the .net regex pattern "v2_\d{4}_[a-zA-Z0-9()_-]+".
Must also otherwise be a valid identifier for a git branch and follow any other project guidelines.
.PARAMETER NoSwitch
If specified, does not automatically switch to your new branch after creating it.
Default is to switch to the new branch after creating it.
.PARAMETER Push
If specified, automatically pushes the new branch to your remote after creating it.
.PARAMETER Remote
The name of the git remote, as configured.
If you never explicitly set this yourself, it is typically "origin".
If you only have one remote defined or have not explicitly set a remote yourself, do not provide this parameter; It will be detected automatically.
.INPUTS
None
.OUTPUTS
The name of the current branch after the operation, as a String.
If NoSwitch was specified and the operation succeeded, this should be the source branch.
If NoSwith was not specified or was explicitly set to $false and the operation succeeded, this should be the new branch.
If an exception occurs, does not return. Exceptions are unhandled and are the responsibility of the caller.
.NOTES
Errors thrown by git commands are not explicitly handled.
#>
Function New-GitBranch {
[CmdletBinding(PositionalBinding=$false, SupportsShouldProcess=$true, ConfirmImpact="Low", DefaultParameterSetName="Basic")]
param(
[Parameter(Mandatory=$true, ParameterSetName="Basic")]
[Parameter(Mandatory=$true, ParameterSetName="NoSwitch")]
[Parameter(Mandatory=$true, ParameterSetName="Push")]
[ValidatePattern("v2_\d{4}_[a-zA-Z0-9()_-]+")]
[string]$Name,
[Parameter(Mandatory=$true,ParameterSetName="NoSwitch",DontShow)]
[switch]$NoSwitch,
[Parameter(Mandatory=$false, ParameterSetName="Basic")]
[Parameter(Mandatory=$true, ParameterSetName="Push")]
[switch]$Push,
[Parameter(Mandatory=$false, ParameterSetName="Push")]
[string]$Remote = $null
)
$currentBranch = (& git branch --show-current)
if(!$PSCmdlet.ShouldProcess("Creating new branch named $Name from $currentBranch", $Name, "Creating branch")) {
return $null
}
git branch $Name
if(!$NoSwitch) {
git switch $Name
if($Push) {
if([String]::IsNullOrWhiteSpace($Remote)) {
$tempRemotes = (git remote show)
if($tempRemotes -is [array]){
# If we've gotten here, Push was specified, a remote was not specified or was blank, and there are multiple remotes defined locally.
# Not going to support that. Just error out.
Remove-Variable tempRemotes
throw "No Remote specified and multiple remotes are defined. Cannot continue."
} else {
# Push is set, Remote wasn't, but there's only one defined. Safe to continue. Use the only remote.
$Remote = $tempRemotes
Remove-Variable tempRemotes
}
}
# Push is set, and either Remote was specified or there's only one remote defined and we will use that.
# Perform the push.
git push --set-upstream $Remote $Name
}
} else{
# NoSwitch was specified.
# Return the current branch name.
return $currentBranch
}
# If we made it to this point, return the Name that was specified.
return $Name
}
<#
.SYNOPSIS
Checks if the command 'git' is available in the current session.
.DESCRIPTION
Checks if the command 'git' is available in the current session.
Throws an error if not.
Returns $true if git is available.
Only intended for use in scripts and module manifests.
.INPUTS
None
.OUTPUTS
If git exists, $true.
Otherwise, $false.
#>
Function Test-GitAvailable {
[OutputType([Boolean])]
[CmdletBinding()]
param()
if($null -eq (Get-Command git -ErrorAction Ignore)) {
Write-Error -Message "git was not found. Git functionality will not work." -Category ObjectNotFound -TargetObject "git"
return $false
}
return $true
}
Test-GitAvailable -ErrorAction Continue

View File

@@ -0,0 +1,150 @@
<#
.SYNOPSIS
All-inclusive module that includes all other Terminal.Gui.PowerShell.* modules.
.DESCRIPTION
All-inclusive module that includes all other Terminal.Gui.PowerShell.* modules.
.EXAMPLE
Import-Module ./Terminal.Gui.PowerShell.psd1
.NOTES
Doc comments on manifest files are not supported by Get-Help as of PowerShell 7.4.2.
This comment block is purely informational and will not interfere with module loading.
#>
@{
# No root module because this is a manifest module.
RootModule = ''
# Version number of this module.
ModuleVersion = '1.0.0'
# Supported PSEditions
CompatiblePSEditions = @('Core')
# ID used to uniquely identify this module
GUID = 'f28198f9-cf4b-4ab0-9f94-aef5616b7989'
# Author of this module
Author = 'Brandon Thetford (GitHub @dodexahedron)'
# Company or vendor of this module
CompanyName = 'The Terminal.Gui Project'
# Copyright statement for this module
Copyright = 'Brandon Thetford (GitHub @dodexahedron), provided to the Terminal.Gui project and you under the MIT license'
# Description of the functionality provided by this module
Description = 'Utilities for development-time operations on and management of components of Terminal.Gui code and other assets.'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '7.4.0'
# Name of the PowerShell "host" subsystem (not system host name). Helps ensure that we know what to expect from the environment.
PowerShellHostName = 'ConsoleHost'
# Minimum version of the PowerShell host required by this module
PowerShellHostVersion = '7.4.0'
# Processor architecture (None, MSIL, X86, IA64, Amd64, Arm, or an empty string) required by this module. One value only.
# Set to AMD64 here because development on Terminal.Gui isn't really supported on anything else.
# Has nothing to do with runtime use of Terminal.Gui.
ProcessorArchitecture = 'Amd64'
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @(
@{
ModuleName='Microsoft.PowerShell.Utility'
ModuleVersion='7.0.0'
},
@{
ModuleName='Microsoft.PowerShell.Management'
ModuleVersion='7.0.0'
},
@{
ModuleName='PSReadLine'
ModuleVersion='2.3.4'
}
)
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of this module.
# This module is just a shortcut that loads all of our modules.
NestedModules = @('./Terminal.Gui.PowerShell.Core.psd1', './Terminal.Gui.PowerShell.Analyzers.psd1', './Terminal.Gui.PowerShell.Git.psd1', './Terminal.Gui.PowerShell.Build.psd1')
# Functions to export from this module.
# Not filtered, so exports all functions exported by all nested modules.
FunctionsToExport = '*'
# Cmdlets to export from this module.
# We don't have any, so empty array.
CmdletsToExport = @()
# Variables to export from this module.
# We explicitly control scope of variables, so empty array.
VariablesToExport = @()
# Aliases to export from this module.
# None defined at this time.
AliasesToExport = @()
# List of all modules packaged with this module
# This is informational ONLY, so it's just blank right now.
# ModuleList = @()
# List of all files packaged with this module
# This is informational ONLY, so it's just blank right now.
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
LicenseUri = 'https://github.com/gui-cs/Terminal.Gui/tree/v2_develop/Scripts/COPYRIGHT'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/gui-cs/Terminal.Gui'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
ReleaseNotes = 'See change history and releases for Terminal.Gui on GitHub'
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}

View File

@@ -0,0 +1,117 @@
#
# Module manifest for module 'Terminal.Gui.Powershell.Analyzers'
#
# Generated by: Brandon Thetford (GitHub @dodexahedron)
#
# Generated on: 4/24/2024
#
@{
# Script module or binary module file associated with this manifest.
RootModule = ''
# Version number of this module.
ModuleVersion = '1.0.0'
# Supported PSEditions
CompatiblePSEditions = @('Core')
# ID used to uniquely identify this module
GUID = '3e85001d-6539-4cf1-b71c-ec9e983f7fc8'
# Author of this module
Author = 'Brandon Thetford (GitHub @dodexahedron)'
# Company or vendor of this module
CompanyName = 'The Terminal.Gui Project'
# Copyright statement for this module
Copyright = '(c) Brandon Thetford (GitHub @dodexahedron). Provided to the Terminal.Gui project and you under the terms of the MIT License.'
# Description of the functionality provided by this module
Description = 'Operations involving Terminal.Gui analyzer projects, fur use during development of Terminal.Gui'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '7.4.0'
# Name of the PowerShell host required by this module
PowerShellHostName = 'ConsoleHost'
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
ProcessorArchitecture = 'Amd64'
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @('Microsoft.PowerShell.Management','Microsoft.PowerShell.Utility','./Terminal.Gui.PowerShell.Core.psd1')
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules.
NestedModules = @('./Terminal.Gui.PowerShell.Analyzers.psm1')
# Functions to export from this module.
FunctionsToExport = @('Build-Analyzers')
# Cmdlets to export from this module.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
LicenseUri = 'https://github.com/gui-cs/Terminal.Gui/Scripts/COPYRIGHT'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/gui-cs/Terminal.Gui'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}