NeVeSpl / NTypewriter

File/code generator using Scriban text templates populated with C# code metadata from Roslyn API.
https://nevespl.github.io/NTypewriter/
MIT License
126 stars 24 forks source link

Save function is misleading #105

Open ViRuSTriNiTy opened 8 months ago

ViRuSTriNiTy commented 8 months ago

Hi there,

first things first, this project is fantastic. It is sooo much easier to use than writing your own source generator with all the tweaks needed to get it right. šŸ‘Œ

So, back to the actual issue I think is worth addressing. When I saw the save function showcased in some samples I thought, oh, really nice, now I don't need to emit, copy / move things around in csproj to get the generated files in the project. I took it to a test and generated some TypeScript code quick and efficently into the project. After a while I came back to generate some C# code in another project and all of a sudden no file was saved anymore. Same setup, almost same template code, except it was C# code. After almost giving up on it I decided to look at the source code and there I found a little detail that makes the Save function a "add source to compilation" function. So, you need to use a different extension to save C# code to a file. Bummer.

My current setup looks like this:

Foobar.cs.nt

# Do something and capture output

# Save the captured output, this will add the source code to the current compilation due to file extension .cs
Save output "Foobar.cs"

# Save it once again to create a temporary file in the project, transition from .cs.tmp to .cs will be done in .csproj
Save output "Foobar.cs.tmp"

Example.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <PackageReference Include="NTypewriter.SourceGenerator" />
  </ItemGroup>

  <!-- NTypewriter.SourceGenerator configuration -->
  <ItemGroup>
    <AdditionalFiles Include="Foobar.cs.nt" />
  </ItemGroup>

  <Target Name="CopyGeneratedFiles" AfterTargets="Build;Rebuild">
    <ItemGroup>
      <GeneratedFoobar Include="$(ProjectDir)Foobar.cs.tmp" />
    </ItemGroup>

    <Move SourceFiles="@(GeneratedFoobar)" DestinationFiles="$(ProjectDir)%(Filename)" />
  </Target>

  <ItemGroup>
    <Compile Remove="Foobar.cs" />
    <None Include="Foobar.cs" />
  </ItemGroup>

</Project>

Another option is to use EmitCompilerGeneratedFiles, but this can result in "path to long" issues and csproj configuration is also more complex.

How can we get rid of the second temporary save? I think something like adding a new function AddSource that does the same checks like Save will do and bring some clarity. Then I would need to call

AddSource
Save

and since Save would then actually write a file I can remove all that csproj magic from the project.

What do you think?

NeVeSpl commented 8 months ago

That behaviour was introduced here #75.

In general, it is a good idea to split Save into two functions: Save and AddSource, but as you already noticed #106 adding *.cs files as a source has some problems right now.

ViRuSTriNiTy commented 8 months ago

I have created a fork in hope to get the requires changes done. But it seems a split is only possible with heavy refactoring or introducing new APIs / deprecating old ones.

My approach would be to deprecate certain APIs and introduce more flexible ones. I think the steps would be

What do you think?

NeVeSpl commented 8 months ago

I would prefer to add a flag AddToCompilation on NTypewriter.RenderedItem instead of using IGeneratedFile as a marking interface, anyway NTypewriter does not have a reference on NTypewriter.Runtime, so using interface there defined is not an option. Passing that flag through the whole execution path should be enough without introducing any new types.