Last active
April 14, 2021 22:49
-
-
Save terrajobst/5d7d48da5af0a4be891d381fd7b2e5ed to your computer and use it in GitHub Desktop.
Split files by preprocessor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice. Glad it helped 😊