Search code examples
c#roslynsourcegenerators

Why shouldn't I read files in a SourceGenerator?


I'm creating a SourceGenerator that will read in a YML file defining the different states of a fluent API and then generate the relevant interfaces to implement the grammar.

I've just seen a warning telling me to add EnforceExtendedAnalyzerRules to my csproj, which I've done. It's now telling me that I shouldn't do File IO in an analyzer.

My main question is:

  1. Why shouldn't I?
  2. How else am I supposed to get the file contents of a file within the project directory as part of a source generator?

Solution

    1. Why shouldn't I?

    If you're running a source generator, you have to understand that the generator will run for every generation phase in the IDE (which may run every key stroke or similar events). You can imagine if your source generator is running that frequently doing I/O operations, your IDE experience will slow down.

    Microsoft have resolved this by releasing the new Incremental Generator API which defers execution of the providers you setup when initializing the generator until there is a change to them, and will cache the results where possible.

    1. How else am I supposed to get the file contents of a file within the project directory as part of a source generator?

    If you're able to register your Yaml file in your project as an additional file in your csproj:

    <ItemGroup>
        <AdditionalFiles Include="myfile.yml" />
    </ItemGroup>
    

    You can get your yml file in the following way (if you are using an Incremental Generator):

    [Generator(Language.CSharp)]
    public class MyGenerator : IIncrementalGenerator
    {
        public void Initialize(IIncrementalGeneratorInitializationContext initContext)
        {
            var ymlFiles = initContext.AdditionalTextProviders.Where(static file => file.Path.EndsWith(".yml"));
    
            var ymlFileContents = ymlFiles.Select((text, cancellationToken) => text.GetText(cancellationToken)!.ToString());
    
            // Optional if you have more registered as an additional file as yml
            var ymlFileContentsToUse = ymlFileContents.Where(content => content.Contains("keyword"));
    
            // Add more providers
    
            // At the end, you can do something like
            // Collect will start evaluating your value providers, and should be called at the end
            var provider = ymlFileContentsToUse.Collect();
    
            initContext.RegisterSourceOutput(provider, (context, source) =>
            {
                // Generate source files here
            });
        }
    }