Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/commands/pr.md
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -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
}
}
}
14 changes: 14 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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

4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ nunit-*.xml

.idea

**/*DotSettings.user
**/*DotSettings.user

**/*.env
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!-- Other useful metadata -->
<Authors>Jonas Ha</Authors>
<PackageTags>csharp;library;utility;source generator</PackageTags>
<PackageTags>csharp;library;utility;SourceGenerator</PackageTags>
<RepositoryUrl>https://github.com/LayeredCraft/source-generator-tools</RepositoryUrl>
<Copyright>Copyright Β© 2025 Jonas Ha</Copyright>
<PackageProjectUrl>https://github.com/LayeredCraft/source-generator-tools</PackageProjectUrl>
Expand All @@ -25,5 +25,6 @@
</ItemGroup>
<PropertyGroup>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>
</Project>
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
<ItemGroup>
<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="PolySharper" Version="0.1.2" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
<PackageVersion Include="AwesomeAssertions" Version="9.3.0" />
<PackageVersion Include="xunit.v3" Version="3.2.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" />
</ItemGroup>
</Project>
138 changes: 138 additions & 0 deletions LayeredCraft.SourceGeneratorTools.sln.DotSettings

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions LayeredCraft.SourceGeneratorTools.slnx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<Solution>
<Folder Name="/examples/">
<Project Path="examples/LayeredCraft.SourceGeneratorTools.BasicExample/LayeredCraft.SourceGeneratorTools.BasicExample.csproj" />
</Folder>
<Folder Name="/Solution Items/">
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path="LICENSE" />
<File Path="README.md" />
</Folder>
<Folder Name="/src/">
<Project Path="src/LayeredCraft.SourceGeneratorTools.Generator/LayeredCraft.SourceGeneratorTools.Generator.csproj" />
<Project Path="src/LayeredCraft.SourceGeneratorTools/LayeredCraft.SourceGeneratorTools.csproj" />
</Folder>
<Folder Name="/test/">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
ο»Ώ<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference
Include="..\..\src\LayeredCraft.SourceGeneratorTools.Generator\LayeredCraft.SourceGeneratorTools.Generator.csproj"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ο»Ώ// See https://aka.ms/new-console-template for more information

using LayeredCraft.SourceGeneratorTools.Types;

Console.WriteLine("Hello, World!");
new EquatableArray<int>();
6 changes: 6 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sdk": {
"rollForward": "latestMinor",
"version": "10.0.101"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace LayeredCraft.SourceGeneratorTools.Generator;

internal readonly record struct GeneratableFeature(string Name, string[] FolderPaths);
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Generic;

namespace LayeredCraft.SourceGeneratorTools.Generator;

internal static class GeneratorConstants
{
internal const string GeneratedCodeHeader = """
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

#nullable enable


""";

internal static readonly Dictionary<string, GeneratableFeature> Features = new()
{
["EquatableArray"] = new GeneratableFeature(
"EquatableArray",
[
"Tools/Types/EquatableArray/EquatableArray.cs",
"Tools/Types/EquatableArray/EquatableArrayExtensions.cs",
"Tools/Utilities/HashCode.cs",
]
),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
ο»Ώ<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<DevelopmentDependency>true</DevelopmentDependency>
<IncludeBuildOutput>false</IncludeBuildOutput>
<PackageId>LayeredCraft.SourceGeneratorTools.Generator</PackageId>
<Description>A generator to create a collection of helpers and tools for working with source generators</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
<PackageReference Include="PolySharp">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Include="..\..\icon.png" Pack="true" PackagePath="" Visible="False" />
<None Include="..\..\README.md" Pack="true" PackagePath="" Visible="False" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource
Include="..\LayeredCraft.SourceGeneratorTools\**\*.cs"
Exclude="..\LayeredCraft.SourceGeneratorTools\obj\**;
..\LayeredCraft.SourceGeneratorTools\bin\**;
..\LayeredCraft.SourceGeneratorTools\.vs\**"
>
<Link>Tools\%(RecursiveDir)%(Filename)%(Extension)</Link>
</EmbeddedResource>
</ItemGroup>
<PropertyGroup>
<!-- Disable PolySharp's ModuleInitializerAttribute shim -->
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.ModuleInitializerAttribute</PolySharpExcludeGeneratedTypes>
</PropertyGroup>
<ItemGroup>
<None
Include="$(OutputPath)\$(AssemblyName).dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"
/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,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);
}
}
});

/// <summary>
/// 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.
/// </summary>
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<PackageId>LayeredCraft.SourceGeneratorTools</PackageId>
<Description>A collection of helpers and tools for working with source generators</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Collections.Immutable" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Collections.Generic;
using System.Collections.Immutable;

// ReSharper disable RedundantNameQualifier

namespace LayeredCraft.SourceGeneratorTools.Types;

/// <summary>
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace LayeredCraft.SourceGeneratorTools.Types;
namespace LayeredCraft.SourceGeneratorTools.Utilities;

/// <summary>Polyfill for .NET 6 HashCode</summary>
internal struct HashCode
public struct HashCode
{
private static readonly uint s_seed = GenerateGlobalSeed();

Expand Down
Empty file added taskfile.yaml
Empty file.
Original file line number Diff line number Diff line change
@@ -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;

Expand Down