Skip to content

AlexChim1231/TypedPaths

Repository files navigation

TypedPaths.Generator

A Roslyn source generator that turns configured folder trees into strongly typed path constants at compile time. Each configured folder becomes a nested static class (for example Src, Template) so you can avoid magic strings.

What you get

Given a structure like:

/src
  Template1.anyext
  folderA/
    Template2.anyext
  folderB/
    Template3.anyext
    Template4.anyext
/template
  email/
    welcome.txt
  sms/
    otp.txt

the generator emits one file per root folder (TypedPaths.Src.g.cs, TypedPaths.Template.g.cs, ...), each defining a top-level static class in the TypedPaths namespace:

// TypedPaths.Src.g.cs
// <auto-generated/>
namespace TypedPaths;

public static class Src
{
    public const string Value = "src";

    public static class Template1
    {
        public const string Value = "src/Template1.anyext";
    }

    public static class FolderA
    {
        public const string Value = "src/folderA";

        public static class Template2
        {
            public const string Value = "src/folderA/Template2.anyext";
        }
    }

    public static class FolderB
    {
        public const string Value = "src/folderB";

        public static class Template3
        {
            public const string Value = "src/folderB/Template3.anyext";
        }

        public static class Template4
        {
            public const string Value = "src/folderB/Template4.anyext";
        }
    }
}
// TypedPaths.Template.g.cs
// <auto-generated/>
namespace TypedPaths;

public static class Template
{
    public const string Value = "template";

    public static class Email
    {
        public const string Value = "template/email";

        public static class Welcome
        {
            public const string Value = "template/email/welcome.txt";
        }
    }

    public static class Sms
    {
        public const string Value = "template/sms";

        public static class Otp
        {
            public const string Value = "template/sms/otp.txt";
        }
    }
}

With using TypedPaths; you can use Src.FolderA.Template2.Value and Template.Email.Welcome.Value instead of raw string paths.

Requirements

  • .NET 8 (or the TFM your project uses; the generator targets .NET Standard 2.0)
  • MSBuild / SDK-style projects

Setup

Option A: consume from NuGet (recommended)

<ItemGroup>
  <PackageReference Include="TypedPaths.Generator" Version="1.0.0" />
</ItemGroup>

<ItemGroup>
  <TypedPathsFolder Include="src" ClassName="Src" />
  <TypedPathsFolder Include="template" />
</ItemGroup>

That is the only configuration needed in consumer projects.
TypedPaths.Generator.targets (from package build) automatically maps each TypedPathsFolder to AdditionalFiles for source generation.

Option B: local project reference (repository development)

<ItemGroup>
  <ProjectReference Include="..\TypedPaths.Generator\TypedPaths.Generator.csproj"
                    OutputItemType="Analyzer"
                    ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
  <TypedPathsFolder Include="src" ClassName="Src" />
  <TypedPathsFolder Include="template" />
</ItemGroup>

<Import Project="..\TypedPaths.Generator\build\TypedPaths.Generator.targets"
        Condition="Exists('..\TypedPaths.Generator\build\TypedPaths.Generator.targets')" />

The explicit <Import /> is required only for local project-reference scenarios.

Build the project; the generator runs and adds TypedPaths.*.g.cs files to the compilation.

Usage in code

Add using TypedPaths; and use the generated classes directly. Each configured root folder becomes a top-level static class (e.g. Src, Template):

using TypedPaths;

// Always read .Value (works for both folder and file nodes)
string folderPath = Src.FolderA.Value;                    // "src/folderA"
string filePath = Src.FolderA.Template2.Value;            // "src/folderA/Template2.anyext"
string emailTemplate = Template.Email.Welcome.Value;     // "template/email/welcome.txt"

// e.g. resolve to full path
var fullPath = Path.Combine(projectRoot, Src.FolderA.Template2.Value);

Value is always a path relative to the project root:

  • Folder node Value = relative folder path
  • File node Value = relative file path (including extension)

Nested child classes are emitted only for folders (because only folders can contain files/subfolders).

Naming rules

  • Folder and file names become PascalCase identifiers.
  • Invalid identifier characters are dropped or split; leading digits get a _ prefix.
  • Duplicate names in the same scope get suffixes: _2, _3, etc.
  • Extensions are stripped from member names but kept in the path string.
  • If a folder name and file name conflict in the same scope:
    • the folder keeps the base name;
    • the file name becomes FileName + ExtensionWithoutDot (example: report/ + report.txt -> Report and ReportTxt);
    • if the conflicting file has no extension, use File suffix (example: data/ + data -> Data and DataFile).

Repository layout

Project Description
TypedPaths.Generator The source generator (Roslyn incremental generator).
TypedPaths.Generator.Sample Example app that uses the generator and runs a small demo.
TypedPaths.Generator.Tests Unit tests for the generator.

Build and test

dotnet restore
dotnet build
dotnet test

Run the sample:

dotnet run --project TypedPaths.Generator.Sample

License

See the repository for license information.

About

Make your Path to be strong typed

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors