Skip to content
Browse files

Fixed roundtripping of preprocessor directives.

  • Loading branch information...
1 parent bf88746 commit 4db74d9da0c3cead1ef58604c24870e62e77810c @dgrunwald dgrunwald committed Nov 26, 2011
View
18 ICSharpCode.NRefactory.CSharp/Ast/GeneralScope/Comment.cs
@@ -26,10 +26,24 @@
namespace ICSharpCode.NRefactory.CSharp
{
- public enum CommentType {
+ public enum CommentType
+ {
+ /// <summary>
+ /// "//" comment
+ /// </summary>
SingleLine,
+ /// <summary>
+ /// "/* */" comment
+ /// </summary>
MultiLine,
- Documentation
+ /// <summary>
+ /// "///" comment
+ /// </summary>
+ Documentation,
+ /// <summary>
+ /// Inactive code (code in non-taken "#if")
+ /// </summary>
+ InactiveCode
}
public class Comment : AstNode, IRelocatable
View
7 ICSharpCode.NRefactory.CSharp/Ast/GeneralScope/PreProcessorDirective.cs
@@ -1,4 +1,4 @@
-//
+//
// PreProcessorDirective.cs
//
// Author:
@@ -64,6 +64,9 @@ public class PreProcessorDirective : AstNode, IRelocatable
set;
}
+ /// <summary>
+ /// For an '#if' directive, specifies whether the condition evaluated to true.
+ /// </summary>
public bool Take {
get;
set;
@@ -107,7 +110,7 @@ void IRelocatable.SetStartLocation (TextLocation startLocation)
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
PreProcessorDirective o = other as PreProcessorDirective;
- return o != null && Type == o.Type;
+ return o != null && Type == o.Type && MatchString(Argument, o.Argument);
}
}
}
View
98 ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -142,7 +142,7 @@ object EndNode (AstNode node)
void WriteSpecials (AstNode start, AstNode end)
{
for (AstNode pos = start; pos != end; pos = pos.NextSibling) {
- if (pos.Role == AstNode.Roles.Comment) {
+ if (pos.Role == AstNode.Roles.Comment || pos.Role == AstNode.Roles.PreProcessorDirective) {
pos.AcceptVisitor (this, null);
}
}
@@ -154,12 +154,21 @@ void WriteSpecials (AstNode start, AstNode end)
/// </summary>
void WriteSpecialsUpToRole (Role role)
{
+ WriteSpecialsUpToRole (role, null);
+ }
+
+ void WriteSpecialsUpToRole (Role role, AstNode nextNode)
+ {
if (positionStack.Count == 0)
return;
- for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) {
+ // Look for the role between the current position and the nextNode.
+ for (AstNode pos = positionStack.Peek(); pos != null && pos != nextNode; pos = pos.NextSibling) {
if (pos.Role == role) {
WriteSpecials (positionStack.Pop (), pos);
- positionStack.Push (pos);
+ // Push the next sibling because the node matching the role is not a special,
+ // and should be considered to be already handled.
+ positionStack.Push (pos.NextSibling);
+ // This is necessary for OptionalComma() to work correctly.
break;
}
}
@@ -176,21 +185,10 @@ void WriteSpecialsUpToNode (AstNode node)
for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) {
if (pos == node) {
WriteSpecials (positionStack.Pop (), pos);
- positionStack.Push (pos);
- break;
- }
- }
- }
-
- void WriteSpecialsUpToRole (Role role, AstNode nextNode)
- {
- if (positionStack.Count == 0)
- return;
- // Look for the role between the current position and the nextNode.
- for (AstNode pos = positionStack.Peek(); pos != null && pos != nextNode; pos = pos.NextSibling) {
- if (pos.Role == AstNode.Roles.Comma) {
- WriteSpecials (positionStack.Pop (), pos);
- positionStack.Push (pos);
+ // Push the next sibling because the node itself is not a special,
+ // and should be considered to be already handled.
+ positionStack.Push (pos.NextSibling);
+ // This is necessary for OptionalComma() to work correctly.
break;
}
}
@@ -212,6 +210,23 @@ void Comma (AstNode nextNode, bool noSpaceAfterComma = false)
Space (!noSpaceAfterComma && policy.SpaceAfterBracketComma); // TODO: Comma policy has changed.
}
+ /// <summary>
+ /// Writes an optional comma, e.g. at the end of an enum declaration
+ /// </summary>
+ void OptionalComma()
+ {
+ // Look for the role between the current position and the nextNode.
+ for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) {
+ if (pos.Role == AstNode.Roles.Comma) {
+ Comma(null, noSpaceAfterComma: true);
+ break;
+ } else if (pos.NodeType != NodeType.Whitespace) {
+ // only skip over whitespace and comma nodes
+ break;
+ }
+ }
+ }
+
void WriteCommaSeparatedList (IEnumerable<AstNode> list)
{
bool isFirst = true;
@@ -1375,22 +1390,22 @@ public object VisitTypeDeclaration (TypeDeclaration typeDeclaration, object data
WriteModifiers (typeDeclaration.ModifierTokens);
BraceStyle braceStyle;
switch (typeDeclaration.ClassType) {
- case ClassType.Enum:
- WriteKeyword ("enum");
- braceStyle = policy.EnumBraceStyle;
- break;
- case ClassType.Interface:
- WriteKeyword ("interface");
- braceStyle = policy.InterfaceBraceStyle;
- break;
- case ClassType.Struct:
- WriteKeyword ("struct");
- braceStyle = policy.StructBraceStyle;
- break;
- default:
- WriteKeyword ("class");
- braceStyle = policy.ClassBraceStyle;
- break;
+ case ClassType.Enum:
+ WriteKeyword ("enum");
+ braceStyle = policy.EnumBraceStyle;
+ break;
+ case ClassType.Interface:
+ WriteKeyword ("interface");
+ braceStyle = policy.InterfaceBraceStyle;
+ break;
+ case ClassType.Struct:
+ WriteKeyword ("struct");
+ braceStyle = policy.StructBraceStyle;
+ break;
+ default:
+ WriteKeyword ("class");
+ braceStyle = policy.ClassBraceStyle;
+ break;
}
WriteIdentifier (typeDeclaration.Name);
WriteTypeParameters (typeDeclaration.TypeParameters);
@@ -1406,9 +1421,6 @@ public object VisitTypeDeclaration (TypeDeclaration typeDeclaration, object data
OpenBrace (braceStyle);
if (typeDeclaration.ClassType == ClassType.Enum) {
bool first = true;
- if (!typeDeclaration.LBraceToken.IsNull) {
- }
-
foreach (var member in typeDeclaration.Members) {
if (first) {
first = false;
@@ -1418,6 +1430,7 @@ public object VisitTypeDeclaration (TypeDeclaration typeDeclaration, object data
}
member.AcceptVisitor (this, data);
}
+ OptionalComma();
NewLine ();
} else {
foreach (var member in typeDeclaration.Members) {
@@ -2263,9 +2276,9 @@ public object VisitComment (Comment comment, object data)
// "1.0 / /*comment*/a", then we need to insert a space in front of the comment.
formatter.Space ();
}
- formatter.StartNode( comment);
+ formatter.StartNode(comment);
formatter.WriteComment (comment.CommentType, comment.Content);
- formatter.EndNode( comment);
+ formatter.EndNode(comment);
lastWritten = LastWritten.Whitespace;
return null;
}
@@ -2278,7 +2291,12 @@ public object VisitPreProcessorDirective (PreProcessorDirective preProcessorDire
formatter.Space ();
}
formatter.StartNode (preProcessorDirective);
- formatter.WriteIdentifier ("#" + preProcessorDirective.Type.ToString ().ToLower ());
+ formatter.WriteToken ("#" + preProcessorDirective.Type.ToString ().ToLower ());
+ if (!string.IsNullOrEmpty(preProcessorDirective.Argument)) {
+ formatter.Space();
+ formatter.WriteToken(preProcessorDirective.Argument);
+ }
+ formatter.NewLine();
formatter.EndNode (preProcessorDirective);
lastWritten = LastWritten.Whitespace;
return null;
View
5 ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs
@@ -175,6 +175,7 @@ public void WriteComment(CommentType commentType, string content)
case CommentType.SingleLine:
textWriter.Write("//");
textWriter.WriteLine(content);
+ needsIndent = true;
break;
case CommentType.MultiLine:
textWriter.Write("/*");
@@ -184,6 +185,10 @@ public void WriteComment(CommentType commentType, string content)
case CommentType.Documentation:
textWriter.Write("///");
textWriter.WriteLine(content);
+ needsIndent = true;
+ break;
+ default:
+ textWriter.Write(content);
break;
}
}
View
68 ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/PreprocessorDirectiveTests.cs
@@ -0,0 +1,68 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+
+namespace ICSharpCode.NRefactory.CSharp.Parser.GeneralScope
+{
+ [TestFixture]
+ public class PreprocessorDirectiveTests
+ {
+ [Test]
+ public void InactiveIf()
+ {
+ string program = @"namespace NS {
+ #if SOMETHING
+ class A {}
+ #endif
+}";
+ NamespaceDeclaration ns = ParseUtilCSharp.ParseGlobal<NamespaceDeclaration>(program);
+ Assert.AreEqual(0, ns.Members.Count);
+ Assert.AreEqual(7, ns.Children.Count());
+
+ Assert.AreEqual(AstNode.Roles.Keyword, ns.Children.ElementAt(0).Role);
+ Assert.AreEqual(AstNode.Roles.Identifier, ns.Children.ElementAt(1).Role);
+ Assert.AreEqual(AstNode.Roles.LBrace, ns.Children.ElementAt(2).Role);
+ Assert.AreEqual(AstNode.Roles.PreProcessorDirective, ns.Children.ElementAt(3).Role);
+ Assert.AreEqual(AstNode.Roles.Comment, ns.Children.ElementAt(4).Role);
+ Assert.AreEqual(AstNode.Roles.PreProcessorDirective, ns.Children.ElementAt(5).Role);
+ Assert.AreEqual(AstNode.Roles.RBrace, ns.Children.ElementAt(6).Role);
+
+ var pp = ns.GetChildrenByRole(AstNode.Roles.PreProcessorDirective);
+
+ Assert.AreEqual(PreProcessorDirectiveType.If, pp.First().Type);
+ Assert.IsFalse(pp.First().Take);
+ Assert.AreEqual("SOMETHING", pp.First().Argument);
+ Assert.AreEqual(new TextLocation(2, 2), pp.First().StartLocation);
+ Assert.AreEqual(new TextLocation(2, 15), pp.First().EndLocation);
+
+ var c = ns.GetChildByRole(AstNode.Roles.Comment);
+ Assert.AreEqual(CommentType.InactiveCode, c.CommentType);
+ Assert.AreEqual(new TextLocation(3, 1), c.StartLocation);
+ Assert.AreEqual(new TextLocation(4, 2), c.EndLocation);
+ Assert.AreEqual("\tclass A {}\n\t", c.Content.Replace("\r", ""));
+
+ Assert.AreEqual(PreProcessorDirectiveType.Endif, pp.Last().Type);
+ Assert.AreEqual(string.Empty, pp.Last().Argument);
+ Assert.AreEqual(new TextLocation(4, 2), pp.First().StartLocation);
+ Assert.AreEqual(new TextLocation(4, 8), pp.First().EndLocation);
+ }
+ }
+}
View
9 ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/TypeDeclarationTests.cs
@@ -329,5 +329,14 @@ public void EnumWithIncorrectNewlineAfterIntegerLiteral()
}
}});
}
+
+ [Test]
+ public void EnumWithCommaAtEnd()
+ {
+ TypeDeclaration td = ParseUtilCSharp.ParseGlobal<TypeDeclaration>("enum MyEnum { A, B, }");
+ Assert.AreEqual(2, td.Members.Count);
+ Assert.AreEqual(AstNode.Roles.RBrace, td.LastChild.Role);
+ Assert.AreEqual(AstNode.Roles.Comma, td.LastChild.PrevSibling.Role);
+ }
}
}
View
1 ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
@@ -86,6 +86,7 @@
<Compile Include="CSharp\Parser\Expression\ObjectCreateExpressionTests.cs" />
<Compile Include="CSharp\Parser\GeneralScope\DelegateDeclarationTests.cs" />
<Compile Include="CSharp\Parser\GeneralScope\NamespaceDeclarationTests.cs" />
+ <Compile Include="CSharp\Parser\GeneralScope\PreprocessorDirectiveTests.cs" />
<Compile Include="CSharp\Parser\GeneralScope\TypeDeclarationTests.cs" />
<Compile Include="CSharp\Parser\GeneralScope\UsingDeclarationTests.cs" />
<Compile Include="CSharp\Parser\ParseSelfTests.cs" />

0 comments on commit 4db74d9

Please sign in to comment.
Something went wrong with that request. Please try again.