diff --git a/.claude/commands/pr.md b/.claude/commands/pr.md
new file mode 100644
index 0000000..aecc2b5
--- /dev/null
+++ b/.claude/commands/pr.md
@@ -0,0 +1,3 @@
+# Open PR for current branch
+- Use this [template](https://github.com/LayeredCraft/.github/blob/main/.github/PULL_REQUEST_TEMPLATE.md) for the PR.
+- $ARGUMENTS
\ No newline at end of file
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..46c33a8
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,20 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "csharpier": {
+ "version": "1.2.3",
+ "commands": [
+ "csharpier"
+ ],
+ "rollForward": false
+ },
+ "jetbrains.resharper.globaltools": {
+ "version": "2025.3.0.4",
+ "commands": [
+ "jb"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f94d1a7
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+max_line_length = 100
+
+[*.cs]
+indent_size = 4
+end_of_line = LF
+insert_final_newline = true
+trim_trailing_whitespace = true
+
diff --git a/.gitignore b/.gitignore
index 61a2cff..b3eebc8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,4 +55,6 @@ nunit-*.xml
.idea
-**/*DotSettings.user
\ No newline at end of file
+**/*DotSettings.user
+
+**/*.env
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 30af134..1fc3423 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,7 +5,7 @@
MIT
Jonas Ha
- csharp;library;utility;source generator
+ csharp;library;utility;SourceGenerator
https://github.com/LayeredCraft/source-generator-tools
Copyright © 2025 Jonas Ha
https://github.com/LayeredCraft/source-generator-tools
@@ -25,5 +25,6 @@
README.md
+ icon.png
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 45b9ef4..f552466 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -6,6 +6,7 @@
+
@@ -13,5 +14,7 @@
+
+
diff --git a/LayeredCraft.SourceGeneratorTools.sln.DotSettings b/LayeredCraft.SourceGeneratorTools.sln.DotSettings
new file mode 100644
index 0000000..ea1af3d
--- /dev/null
+++ b/LayeredCraft.SourceGeneratorTools.sln.DotSettings
@@ -0,0 +1,138 @@
+
+ <?xml version="1.0" encoding="utf-16"?><Profile name="Full Custom Cleanup"><CppReformatCode>True</CppReformatCode><FSharpReformatCode>True</FSharpReformatCode><ShaderLabReformatCode>True</ShaderLabReformatCode><XMLReformatCode>True</XMLReformatCode><VBReformatCode>True</VBReformatCode><CSReformatCode>True</CSReformatCode><CSharpReformatComments>True</CSharpReformatComments><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortDefinitions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CSReorderTypeMembers>True</CSReorderTypeMembers><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppRedundantDereferences>True</CppRedundantDereferences><CppDeleteRedundantAccessSpecifier>True</CppDeleteRedundantAccessSpecifier><CppRemoveCastDescriptor>True</CppRemoveCastDescriptor><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppMakeVariableConstexpr>True</CppMakeVariableConstexpr><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppTypeTraitAliasDescriptor>True</CppTypeTraitAliasDescriptor><CppRemoveRedundantConditionalExpressionDescriptor>True</CppRemoveRedundantConditionalExpressionDescriptor><CppSimplifyConditionalExpressionDescriptor>True</CppSimplifyConditionalExpressionDescriptor><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppReplaceTieWithStructuredBindingDescriptor>True</CppReplaceTieWithStructuredBindingDescriptor><CppUseAssociativeContainsDescriptor>True</CppUseAssociativeContainsDescriptor><CppUseEraseAlgorithmDescriptor>True</CppUseEraseAlgorithmDescriptor><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeClassFinal>True</CppMakeClassFinal><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppMakePtrOrRefParameterConst>True</CppMakePtrOrRefParameterConst><CppMakeParameterConst>True</CppMakeParameterConst><CppPassValueParameterByConstReference>True</CppPassValueParameterByConstReference><CppRemoveElaboratedTypeSpecifierDescriptor>True</CppRemoveElaboratedTypeSpecifierDescriptor><CppRemoveRedundantLambdaParameterListDescriptor>True</CppRemoveRedundantLambdaParameterListDescriptor><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppReplaceIfWithIfConsteval>True</CppReplaceIfWithIfConsteval><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><VBMakeFieldReadonly>True</VBMakeFieldReadonly><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><IDEA_SETTINGS><profile version="1.0">
+ <option name="myName" value="Full Custom Cleanup" />
+ <inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="JSRemoveUnnecessaryParentheses" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
+</profile></IDEA_SETTINGS><RIDER_SETTINGS><profile>
+ <Language id="CSS">
+ <Rearrange>true</Rearrange>
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="EditorConfig">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="HCL">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="HTML">
+ <OptimizeImports>true</OptimizeImports>
+ <Rearrange>true</Rearrange>
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="HTTP Request">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Handlebars">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Ini">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="JSON">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Jade">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="JavaScript">
+ <OptimizeImports>true</OptimizeImports>
+ <Rearrange>true</Rearrange>
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Markdown">
+ <Reformat>false</Reformat>
+ </Language>
+ <Language id="Mermaid">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="PowerShell">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Properties">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="RELAX-NG">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Razor">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="SQL">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="TOML">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="VueExpr">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="XML">
+ <OptimizeImports>true</OptimizeImports>
+ <Rearrange>true</Rearrange>
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="yaml">
+ <Reformat>true</Reformat>
+ </Language>
+</profile></RIDER_SETTINGS><CSharpFormatDocComments>True</CSharpFormatDocComments><RemoveCodeRedundancies>True</RemoveCodeRedundancies></Profile>
+ Built-in: Full Cleanup
+ NotRequired
+ NotRequired
+ NotRequired
+ NotRequired
+ ExpressionBody
+ ExpressionBody
+ ExpressionBody
+ True
+ False
+ False
+ False
+ False
+ False
+ False
+ False
+ True
+ 1
+ ALWAYS
+ ALWAYS
+ ALWAYS
+ False
+ NEVER
+ False
+ False
+ ALWAYS_IF_MULTILINE
+ True
+ True
+ CHOP_IF_LONG
+ False
+ True
+ True
+ True
+ True
+ True
+ CHOP_IF_LONG
+ True
+ CHOP_IF_LONG
+ CHOP_IF_LONG
+ 2
+ False
+ False
+ 2
+ ByFirstAttr
+ True
+ False
+ False
+ True
+ False
+ False
+ True
+ False
+ False
+ True
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/LayeredCraft.SourceGeneratorTools.slnx b/LayeredCraft.SourceGeneratorTools.slnx
index 8cd28e9..61b75cb 100644
--- a/LayeredCraft.SourceGeneratorTools.slnx
+++ b/LayeredCraft.SourceGeneratorTools.slnx
@@ -1,4 +1,7 @@
+
+
+
@@ -6,6 +9,7 @@
+
diff --git a/examples/LayeredCraft.SourceGeneratorTools.BasicExample/LayeredCraft.SourceGeneratorTools.BasicExample.csproj b/examples/LayeredCraft.SourceGeneratorTools.BasicExample/LayeredCraft.SourceGeneratorTools.BasicExample.csproj
new file mode 100644
index 0000000..69590d4
--- /dev/null
+++ b/examples/LayeredCraft.SourceGeneratorTools.BasicExample/LayeredCraft.SourceGeneratorTools.BasicExample.csproj
@@ -0,0 +1,17 @@
+
+
+ Exe
+ net10.0
+ preview
+ enable
+ enable
+ false
+
+
+
+
+
diff --git a/examples/LayeredCraft.SourceGeneratorTools.BasicExample/Program.cs b/examples/LayeredCraft.SourceGeneratorTools.BasicExample/Program.cs
new file mode 100644
index 0000000..2924520
--- /dev/null
+++ b/examples/LayeredCraft.SourceGeneratorTools.BasicExample/Program.cs
@@ -0,0 +1,6 @@
+// See https://aka.ms/new-console-template for more information
+
+using LayeredCraft.SourceGeneratorTools.Types;
+
+Console.WriteLine("Hello, World!");
+new EquatableArray();
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..38f47fa
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "rollForward": "latestMinor",
+ "version": "10.0.101"
+ }
+}
\ No newline at end of file
diff --git a/src/LayeredCraft.SourceGeneratorTools.Generator/GeneratableFeature.cs b/src/LayeredCraft.SourceGeneratorTools.Generator/GeneratableFeature.cs
new file mode 100644
index 0000000..5fb8897
--- /dev/null
+++ b/src/LayeredCraft.SourceGeneratorTools.Generator/GeneratableFeature.cs
@@ -0,0 +1,3 @@
+namespace LayeredCraft.SourceGeneratorTools.Generator;
+
+internal readonly record struct GeneratableFeature(string Name, string[] FolderPaths);
diff --git a/src/LayeredCraft.SourceGeneratorTools.Generator/GeneratorConstants.cs b/src/LayeredCraft.SourceGeneratorTools.Generator/GeneratorConstants.cs
new file mode 100644
index 0000000..e2d09b1
--- /dev/null
+++ b/src/LayeredCraft.SourceGeneratorTools.Generator/GeneratorConstants.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+
+namespace LayeredCraft.SourceGeneratorTools.Generator;
+
+internal static class GeneratorConstants
+{
+ internal const string GeneratedCodeHeader = """
+ //------------------------------------------------------------------------------
+ //
+ // This code was generated by a tool.
+ //
+ // Changes to this file may cause incorrect behavior and will be lost if
+ // the code is regenerated.
+ //
+ //------------------------------------------------------------------------------
+
+ #nullable enable
+
+
+ """;
+
+ internal static readonly Dictionary Features = new()
+ {
+ ["EquatableArray"] = new GeneratableFeature(
+ "EquatableArray",
+ [
+ "Tools/Types/EquatableArray/EquatableArray.cs",
+ "Tools/Types/EquatableArray/EquatableArrayExtensions.cs",
+ "Tools/Utilities/HashCode.cs",
+ ]
+ ),
+ };
+}
diff --git a/src/LayeredCraft.SourceGeneratorTools.Generator/LayeredCraft.SourceGeneratorTools.Generator.csproj b/src/LayeredCraft.SourceGeneratorTools.Generator/LayeredCraft.SourceGeneratorTools.Generator.csproj
new file mode 100644
index 0000000..e37c386
--- /dev/null
+++ b/src/LayeredCraft.SourceGeneratorTools.Generator/LayeredCraft.SourceGeneratorTools.Generator.csproj
@@ -0,0 +1,48 @@
+
+
+ netstandard2.0
+ preview
+ enable
+ Library
+ true
+ true
+ true
+ false
+ LayeredCraft.SourceGeneratorTools.Generator
+ A generator to create a collection of helpers and tools for working with source generators
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+ Tools\%(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ System.Runtime.CompilerServices.ModuleInitializerAttribute
+
+
+
+
+
diff --git a/src/LayeredCraft.SourceGeneratorTools.Generator/SourceGeneratorToolsGenerator.cs b/src/LayeredCraft.SourceGeneratorTools.Generator/SourceGeneratorToolsGenerator.cs
new file mode 100644
index 0000000..eaa1bb4
--- /dev/null
+++ b/src/LayeredCraft.SourceGeneratorTools.Generator/SourceGeneratorToolsGenerator.cs
@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace LayeredCraft.SourceGeneratorTools.Generator;
+
+[Generator]
+public class SourceGeneratorToolsGenerator : IIncrementalGenerator
+{
+ private const string PublicModifier = "public";
+ private const string InternalModifier = "internal";
+ private const string NoReplaceComment = "// no-replace";
+ private const int PublicModifierLength = 6;
+
+ public void Initialize(IncrementalGeneratorInitializationContext context) =>
+ context.RegisterPostInitializationOutput(ctx =>
+ {
+ // For now, hardcode to EquatableArray - in the future this could be configurable
+ var featureKeys = new[] { "EquatableArray" };
+
+ var assembly = typeof(SourceGeneratorToolsGenerator).Assembly;
+
+ foreach (var featureKey in featureKeys)
+ {
+ if (!GeneratorConstants.Features.TryGetValue(featureKey, out var feature))
+ continue;
+
+ // Get the assembly namespace for resource lookup
+ var assemblyNamespace = assembly.GetName().Name;
+
+ // Load each file path specified in the feature
+ foreach (var filePath in feature.FolderPaths)
+ {
+ // Convert file path to embedded resource name (add assembly namespace and
+ // replace / with .)
+ var resourceName = $"{assemblyNamespace}.{filePath.Replace('/', '.')}";
+
+ using var stream = assembly.GetManifestResourceStream(resourceName);
+ if (stream == null)
+ continue;
+
+ var content = ConvertPublicToInternal(stream);
+
+ // Extract filename from the path
+ var fileName = Path.GetFileName(filePath);
+ var generatedFileName = Path.GetFileNameWithoutExtension(fileName) + ".g.cs";
+
+ var source = GeneratorConstants.GeneratedCodeHeader + content;
+
+ ctx.AddSource(generatedFileName, source);
+ }
+ }
+ });
+
+ ///
+ /// Sets any line that starts with "public" to be "internal". This only impacts top-level
+ /// statements. This behavior can be skipped by adding a "// no-replace" at the end of the line.
+ ///
+ private static string ConvertPublicToInternal(Stream stream)
+ {
+ var sb = new StringBuilder((int)stream.Length);
+ using var reader = new StreamReader(stream);
+
+ while (reader.ReadLine() is { } line)
+ if (
+ line.StartsWith(PublicModifier)
+ && !line.Contains(NoReplaceComment, StringComparison.Ordinal)
+ )
+ sb.Append(InternalModifier).AppendLine(line[PublicModifierLength..]);
+ else
+ sb.AppendLine(line);
+
+ return sb.ToString();
+ }
+}
diff --git a/src/LayeredCraft.SourceGeneratorTools/LayeredCraft.SourceGeneratorTools.csproj b/src/LayeredCraft.SourceGeneratorTools/LayeredCraft.SourceGeneratorTools.csproj
index 0fb88a3..b36d1ee 100644
--- a/src/LayeredCraft.SourceGeneratorTools/LayeredCraft.SourceGeneratorTools.csproj
+++ b/src/LayeredCraft.SourceGeneratorTools/LayeredCraft.SourceGeneratorTools.csproj
@@ -4,6 +4,8 @@
preview
enable
Library
+ LayeredCraft.SourceGeneratorTools
+ A collection of helpers and tools for working with source generators
diff --git a/src/LayeredCraft.SourceGeneratorTools/Types/EquatableArray.cs b/src/LayeredCraft.SourceGeneratorTools/Types/EquatableArray/EquatableArray.cs
similarity index 96%
rename from src/LayeredCraft.SourceGeneratorTools/Types/EquatableArray.cs
rename to src/LayeredCraft.SourceGeneratorTools/Types/EquatableArray/EquatableArray.cs
index 0ab266a..08ee32e 100644
--- a/src/LayeredCraft.SourceGeneratorTools/Types/EquatableArray.cs
+++ b/src/LayeredCraft.SourceGeneratorTools/Types/EquatableArray/EquatableArray.cs
@@ -6,6 +6,8 @@
using System.Collections.Generic;
using System.Collections.Immutable;
+// ReSharper disable RedundantNameQualifier
+
namespace LayeredCraft.SourceGeneratorTools.Types;
///
@@ -37,7 +39,7 @@ public override int GetHashCode()
if (_array is not T[] array)
return 0;
- HashCode hashCode = default;
+ LayeredCraft.SourceGeneratorTools.Utilities.HashCode hashCode = default;
foreach (var item in array)
hashCode.Add(item);
diff --git a/src/LayeredCraft.SourceGeneratorTools/Types/EquatableArrayExtensions.cs b/src/LayeredCraft.SourceGeneratorTools/Types/EquatableArray/EquatableArrayExtensions.cs
similarity index 100%
rename from src/LayeredCraft.SourceGeneratorTools/Types/EquatableArrayExtensions.cs
rename to src/LayeredCraft.SourceGeneratorTools/Types/EquatableArray/EquatableArrayExtensions.cs
diff --git a/src/LayeredCraft.SourceGeneratorTools/Types/HashCode.cs b/src/LayeredCraft.SourceGeneratorTools/Utilities/HashCode.cs
similarity index 99%
rename from src/LayeredCraft.SourceGeneratorTools/Types/HashCode.cs
rename to src/LayeredCraft.SourceGeneratorTools/Utilities/HashCode.cs
index 4ec9b86..d6b50ac 100644
--- a/src/LayeredCraft.SourceGeneratorTools/Types/HashCode.cs
+++ b/src/LayeredCraft.SourceGeneratorTools/Utilities/HashCode.cs
@@ -3,10 +3,10 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
-namespace LayeredCraft.SourceGeneratorTools.Types;
+namespace LayeredCraft.SourceGeneratorTools.Utilities;
/// Polyfill for .NET 6 HashCode
-internal struct HashCode
+public struct HashCode
{
private static readonly uint s_seed = GenerateGlobalSeed();
diff --git a/taskfile.yaml b/taskfile.yaml
new file mode 100644
index 0000000..e69de29
diff --git a/test/LayeredCraft.SourceGeneratorTools.UnitTests/Types/HashCodeTests.cs b/test/LayeredCraft.SourceGeneratorTools.UnitTests/Types/HashCodeTests.cs
index 4eebe00..c5f291c 100644
--- a/test/LayeredCraft.SourceGeneratorTools.UnitTests/Types/HashCodeTests.cs
+++ b/test/LayeredCraft.SourceGeneratorTools.UnitTests/Types/HashCodeTests.cs
@@ -1,6 +1,6 @@
using AwesomeAssertions;
using JetBrains.Annotations;
-using HashCode = LayeredCraft.SourceGeneratorTools.Types.HashCode;
+using HashCode = LayeredCraft.SourceGeneratorTools.Utilities.HashCode;
namespace LayeredCraft.SourceGeneratorTools.UnitTests.Types;