-
-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Start of better handling of #if directives
closes #404
- Loading branch information
Showing
2 changed files
with
214 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
using System; | ||
using System.Linq; | ||
using FluentAssertions; | ||
using NUnit.Framework; | ||
|
||
namespace CSharpier.Tests; | ||
|
||
[TestFixture] | ||
public class ConditionalServiceTests | ||
{ | ||
[Test] | ||
public void GetSymbolSets_Should_Handle_Basic_If() | ||
{ | ||
RunTest( | ||
@"#if DEBUG | ||
public class Tester { } | ||
#endif | ||
", | ||
new[] { "DEBUG" } | ||
); | ||
} | ||
|
||
[Test] | ||
public void GetSymbolSets_Should_Handle_And() | ||
{ | ||
RunTest( | ||
@"#if ONE && TWO | ||
public class Tester { } | ||
#endif | ||
", | ||
new[] { "DEBUG" } | ||
); | ||
} | ||
|
||
[Test] | ||
public void GetSymbolSets_Should_Handle_Or() | ||
{ | ||
RunTest( | ||
@"#if ONE || TWO | ||
public class Tester { } | ||
#endif | ||
", | ||
new[] { "DEBUG" } | ||
); | ||
} | ||
|
||
[Test] | ||
public void GetSymbolSets_Should_Handle_Basic_Not_If() | ||
{ | ||
RunTest( | ||
@"#if !DEBUG | ||
public class Tester { } | ||
#endif | ||
", | ||
Array.Empty<string>() | ||
); | ||
} | ||
|
||
[Test] | ||
public void GetSymbolSets_Should_Handle_Basic_If_With_Else() | ||
{ | ||
RunTest( | ||
@"#if DEBUG | ||
public class Tester { } | ||
#else | ||
public class Tester2 { } | ||
#endif | ||
", | ||
new[] { "DEBUG" }, | ||
Array.Empty<string>() | ||
); | ||
} | ||
|
||
[Test] | ||
public void GetSymbolSets_Should_Handle_Basic_If_With_ElseIf() | ||
{ | ||
RunTest( | ||
@"#if ONE | ||
public class Tester { } | ||
#elif TWO | ||
public class Tester2 { } | ||
#endif | ||
", | ||
new[] { "ONE" }, | ||
new[] { "TWO" } | ||
); | ||
} | ||
|
||
private void RunTest(string code, params string[][] symbolSets) | ||
{ | ||
var result = ConditionalService.GetSymbolSets(code); | ||
|
||
result.Should().HaveCount(symbolSets.Length); | ||
for (var x = 0; x < symbolSets.Length; x++) | ||
{ | ||
result[x].Should().HaveCount(symbolSets[x].Length); | ||
foreach (var symbol in symbolSets[x]) | ||
{ | ||
result[x].Should().Contain(symbol); | ||
} | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
namespace CSharpier; | ||
|
||
public class ConditionalService | ||
{ | ||
// TODO this can probably take a SyntaxTree to make it more efficient | ||
public static List<string[]> GetSymbolSets(string code) | ||
{ | ||
var tree = CSharpSyntaxTree.ParseText(code); | ||
|
||
var customWalker = new CustomWalker(); | ||
customWalker.Visit(tree.GetRoot()); | ||
|
||
return customWalker.GetSymbolSets(); | ||
} | ||
|
||
private class CustomWalker : CSharpSyntaxWalker | ||
{ | ||
public CustomWalker() : base(SyntaxWalkerDepth.Trivia) { } | ||
|
||
private List<string[]> symbolSets = new(); | ||
|
||
public List<string[]> GetSymbolSets() | ||
{ | ||
return this.symbolSets; | ||
} | ||
|
||
// TODO test with real code!!! | ||
// TODO could we optimize even more? do we need to visit trailing trivia? | ||
public override void VisitLeadingTrivia(SyntaxToken token) | ||
{ | ||
if (token.HasLeadingTrivia) | ||
{ | ||
foreach (var tr in token.LeadingTrivia) | ||
{ | ||
if ( | ||
tr.RawSyntaxKind() | ||
is SyntaxKind.IfDirectiveTrivia | ||
or SyntaxKind.ElifDirectiveTrivia | ||
or SyntaxKind.ElseDirectiveTrivia | ||
or SyntaxKind.EndIfDirectiveTrivia | ||
) | ||
{ | ||
this.Visit((CSharpSyntaxNode)tr.GetStructure()!); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public override void VisitIfDirectiveTrivia(IfDirectiveTriviaSyntax node) | ||
{ | ||
DoThing(node.Condition); | ||
} | ||
|
||
public override void VisitElifDirectiveTrivia(ElifDirectiveTriviaSyntax node) | ||
{ | ||
DoThing(node.Condition); | ||
} | ||
|
||
// TODO make use of the walker itself for this? | ||
// TODO ( ) | ||
// TODO ! ! | ||
// TODO mix of && and || | ||
// TODO nested ifs | ||
// TODO ideally we narrow down to the best set of symbols to limit the formatting passes | ||
private void DoThing(ExpressionSyntax expression) | ||
{ | ||
if (expression is IdentifierNameSyntax identifierNameSyntax) | ||
{ | ||
this.symbolSets.Add(new[] { identifierNameSyntax.Identifier.ToString() }); | ||
} | ||
else if (expression is PrefixUnaryExpressionSyntax prefixUnaryExpressionSyntax) | ||
{ | ||
if ( | ||
prefixUnaryExpressionSyntax.OperatorToken.RawSyntaxKind() | ||
is SyntaxKind.ExclamationToken | ||
) | ||
{ | ||
if ( | ||
prefixUnaryExpressionSyntax.Operand | ||
is IdentifierNameSyntax identifierNameSyntax2 | ||
) | ||
{ | ||
this.symbolSets.Add(Array.Empty<string>()); | ||
} | ||
} | ||
else | ||
{ | ||
Console.WriteLine("Can't handle " + prefixUnaryExpressionSyntax.OperatorToken); | ||
} | ||
} | ||
else if (expression is BinaryExpressionSyntax binaryExpressionSyntax) | ||
{ | ||
throw new Exception("BINARY EXPRESSIONS!"); | ||
} | ||
else | ||
{ | ||
Console.WriteLine("Can't handle " + expression.GetType()); | ||
} | ||
} | ||
|
||
public override void VisitElseDirectiveTrivia(ElseDirectiveTriviaSyntax node) | ||
{ | ||
// TODO | ||
} | ||
|
||
public override void VisitEndIfDirectiveTrivia(EndIfDirectiveTriviaSyntax node) | ||
{ | ||
// TODO | ||
} | ||
} | ||
} |