iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🔀

Implementing Custom Directives with Roslyn

に公開
Prequel

In a Unity environment, the #: directive used for .NET 10 single-file execution causes a compilation error. (Interestingly, #! at the beginning of a file works just fine.)

Even if you move the test scripts themselves to folders like Tests~ which aren't imported by Unity, the problem remains that references are difficult because Unity projects write all .csproj files to the root. Furthermore, .csproj files generated by Unity might not be buildable in VS Code.

Also, in Visual Studio Insiders, IntelliSense doesn't work for single-file execution .cs files. However, it does work in VS Code, even if it (might) not be able to build them.

In short, it's a messy transition period, so if a class has no dependency on Unity, why not just import it directly! So, I created a custom preprocessor directive-like syntax using a Source Generator.

Usage

#:package FUnit@*
#:package FUnit.Directives@*

// 👇 Referencing Directives allows using //:funit:
//:funit:include ../Runtime/MyClass.cs

return await FUnit.RunAsync(args, describe =>
{
    describe("Test", it =>
    {
        it("should work", () =>
        {
            // Since the source generator outputs it as if it read and generated the file normally,
            // you can test even internal members!
            Must.BeEqual(310, MyClass.Method());
            //                ~~~~~~~~~~~~~~ Type and method included from MyClass.cs
        })
    });
});

include Directive

//:funit:include <target_file.cs>  *Must be at the beginning of the line

It treats the argument (?) as a relative path from the .cs file, simply reads it with System.IO.File, and outputs it directly as "generated source code" from the Source Generator.

Whether it's to avoid circular references in the project, because it's not referenced at all, or across any other boundaries, you can quickly import specified files.

Notes

Roslyn has an error diagnostic called RS1035, but I'm building it as if it weren't there.

When implementing the directive, I chose ISourceGenerator because IIncrementalGenerator has difficulty detecting code deletion. Since it's only executed once for "single-file execution," I don't think it will be a problem.

It seems to be an error limited to IIncrementalGenerator. In other words, include is legal even in C#!

Besides that, while I perform syntax checks and file existence checks, I don't verify if the file being loaded is appropriate at all, so if you load the file itself, everything becomes ambiguous. (Including the same file multiple times is fine.)

👇 Original source (Everything becomes ambiguous)

👇 Generated source (Duplicate names, etc.)

Conclusion

There was a request on GitHub to add an #:item directive, so I feel like include support will be added eventually. As it stands now, it feels like it's just one step away from being complete!

Please use this as a stopgap until the official support is available.

That's all. Thank you.

Discussion