Skip to content

Commit

Permalink
Start of better handling of #if directives
Browse files Browse the repository at this point in the history
closes #404
  • Loading branch information
belav committed May 13, 2023
1 parent 099a7b5 commit 5c19060
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 0 deletions.
103 changes: 103 additions & 0 deletions Src/CSharpier.Tests/ConditionalServiceTests.cs
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);
}
}
}
}
111 changes: 111 additions & 0 deletions Src/CSharpier/ConditionalService.cs
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
}
}
}

0 comments on commit 5c19060

Please sign in to comment.