Skip to content

Instantly share code, notes, and snippets.

@terrajobst
Last active April 14, 2021 22:49
Show Gist options
  • Save terrajobst/5d7d48da5af0a4be891d381fd7b2e5ed to your computer and use it in GitHub Desktop.
Save terrajobst/5d7d48da5af0a4be891d381fd7b2e5ed to your computer and use it in GitHub Desktop.
Split files by preprocessor
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace ConditionalCompilationSplitter
{
class Program
{
static void Main(string[] args)
{
var sourcePath = @"D:\temp\input.cs";
var outputDirectory = @"D:\temp\";
// First, let's parse the input file as-is. We do this so that we
// can find all the processor symbols
var source = File.ReadAllText(sourcePath);
var syntaxTree = CSharpSyntaxTree.ParseText(source);
foreach (var identifier in GetReferencedPreprocessorSymbols(syntaxTree))
{
// OK, now reparse the file with this preprocessor symbol defined.
var options = CSharpParseOptions.Default.WithPreprocessorSymbols(identifier);
var newTree = CSharpSyntaxTree.ParseText(source, options);
// Now strip all preprocessor symbols, including all text that was disabled.
// The end result is syntax tree that only contains code that is active under
// this symbol.
var strippedTree = PreprocessorStripper.Strip(newTree);
// Persist the file. We'll simply put in a folder that is named by the precessor
// symbol. Don't shoot me if the symbol isn't a valid path.
var path = Path.Combine(outputDirectory, identifier, Path.GetFileName(sourcePath));
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var writer = new StreamWriter(path))
strippedTree.GetText().Write(writer);
}
}
// Return all referenced preprocessor symbols. You could also just hard code the ones
// you care about.
static IEnumerable<string> GetReferencedPreprocessorSymbols(SyntaxTree syntaxTree)
{
return syntaxTree.GetRoot()
.DescendantTrivia()
.Where(t => t.Kind() == SyntaxKind.IfDirectiveTrivia ||
t.Kind() == SyntaxKind.ElifDirectiveTrivia)
.Select(t => t.GetStructure())
.Cast<ConditionalDirectiveTriviaSyntax>()
.SelectMany(c => c.Condition.DescendantNodesAndSelf().OfType<IdentifierNameSyntax>())
.Select(i => i.Identifier.ValueText)
.Distinct()
.OrderBy(i => i);
}
// Syntax rewriter that nukes all preprocessor directives and text that is
// active i.e under a preprocessor directive that evaluated to true.
class PreprocessorStripper : CSharpSyntaxRewriter
{
private PreprocessorStripper()
{
}
public static SyntaxTree Strip(SyntaxTree syntaxTree)
{
var stripper = new PreprocessorStripper();
var newRoot = stripper.Visit(syntaxTree.GetRoot());
return syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options);
}
public override bool VisitIntoStructuredTrivia => true;
public override SyntaxTrivia VisitTrivia(SyntaxTrivia trivia)
{
if (trivia.Kind() == SyntaxKind.DisabledTextTrivia)
return SyntaxFactory.Whitespace("");
return base.VisitTrivia(trivia);
}
public override SyntaxNode VisitIfDirectiveTrivia(IfDirectiveTriviaSyntax node)
{
return null;
}
public override SyntaxNode VisitElifDirectiveTrivia(ElifDirectiveTriviaSyntax node)
{
return null;
}
public override SyntaxNode VisitElseDirectiveTrivia(ElseDirectiveTriviaSyntax node)
{
return null;
}
public override SyntaxNode VisitEndIfDirectiveTrivia(EndIfDirectiveTriviaSyntax node)
{
return null;
}
}
}
}
@terrajobst
Copy link
Author

Nice. Glad it helped 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment