diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/RemoveBackingStoreAction.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/RemoveBackingStoreAction.cs index 4074394aa..504294d0b 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/RemoveBackingStoreAction.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/RemoveBackingStoreAction.cs @@ -53,7 +53,19 @@ public override IEnumerable GetActions(RefactoringContext context) yield return new CodeAction(context.TranslateString("Convert to auto property"), script => { script.Rename((IEntity)field, newProperty.Name); - script.Remove (context.RootNode.GetNodeAt (field.Region.Begin)); + var oldField = context.RootNode.GetNodeAt(field.Region.Begin); + if (oldField.Variables.Count == 1) { + script.Remove(oldField); + } else { + var newField = (FieldDeclaration)oldField.Clone(); + foreach (var init in newField.Variables) { + if (init.Name == field.Name) { + init.Remove(); + break; + } + } + script.Replace(oldField, newField); + } script.Replace (property, newProperty); }, property.NameToken); } @@ -135,6 +147,9 @@ internal static IField ScanSetter (BaseRefactoringContext context, PropertyDecla var assignment = setAssignment != null ? setAssignment.Expression as AssignmentExpression : null; if (assignment == null || assignment.Operator != AssignmentOperatorType.Assign) return null; + var idExpr = assignment.Right as IdentifierExpression; + if (idExpr == null || idExpr.Identifier != "value") + return null; if (!IsPossibleExpression(assignment.Left)) return null; var result = context.Resolve (assignment.Left); diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/UseStringFormatAction.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/UseStringFormatAction.cs index 51112a012..2a299bb78 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/UseStringFormatAction.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/UseStringFormatAction.cs @@ -114,7 +114,7 @@ public override IEnumerable GetActions(RefactoringContext context) format.Append('"'); if (verbatim) format.Insert(0, '@'); - formatLiteral.LiteralValue = format.ToString(); + formatLiteral.SetValue(format.ToString(), format.ToString()); if (arguments.Count > 0) script.Replace(expr, formatInvocation); else diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/MethodOverloadWithOptionalParameterIssue.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/MethodOverloadWithOptionalParameterIssue.cs index 1aafa2d88..beb91ae5d 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/MethodOverloadWithOptionalParameterIssue.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/MethodOverloadWithOptionalParameterIssue.cs @@ -89,7 +89,7 @@ public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration) return; CheckParameters (method, method.DeclaringType.GetMethods(m => - m.Name == method.Name).Cast().ToList(), + m.Name == method.Name && m.TypeParameters.Count == method.TypeParameters.Count).Cast().ToList(), methodDeclaration.Parameters.ToList() ); diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/OptionalParameterHierarchyMismatchIssue.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/OptionalParameterHierarchyMismatchIssue.cs index 70c7a6c80..557b03bfc 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/OptionalParameterHierarchyMismatchIssue.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/OptionalParameterHierarchyMismatchIssue.cs @@ -62,6 +62,9 @@ public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration) void VisitParameterizedEntityDeclaration(string memberType, EntityDeclaration entityDeclaration, AstNodeCollection parameters) { + // Ignore explicit interface implementations (those should have no optional parameters as there can't be any direct calls) + if (!entityDeclaration.GetChildByRole(EntityDeclaration.PrivateImplementationTypeRole).IsNull) + return; //Override is not strictly necessary because methodDeclaration //might still implement an interface member var memberResolveResult = ctx.Resolve(entityDeclaration) as MemberResolveResult; diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/PossibleAssignmentToReadonlyFieldIssue.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/PossibleAssignmentToReadonlyFieldIssue.cs index 7e5afce5d..ac7b51769 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/PossibleAssignmentToReadonlyFieldIssue.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/CodeQuality/PossibleAssignmentToReadonlyFieldIssue.cs @@ -32,8 +32,8 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring { [IssueDescription ( - "Check if a namespace corresponds to a file location", - Description = "Check if a namespace corresponds to a file location", + "Possible assignment to readonly field", + Description = "Check if a readonly field is used as assignment target", Category = IssueCategories.CodeQualityIssues, Severity = Severity.Warning, AnalysisDisableKeyword = "PossibleAssignmentToReadonlyField")] diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/RedundanciesInCode/RedundantExplicitArrayCreationIssue.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/RedundanciesInCode/RedundantExplicitArrayCreationIssue.cs index 979d511f8..b86cedfe4 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/RedundanciesInCode/RedundantExplicitArrayCreationIssue.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/RedundanciesInCode/RedundantExplicitArrayCreationIssue.cs @@ -32,8 +32,8 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring { [IssueDescription( - "Redundant explicit size in array creation", - Description = "Redundant explicit size in array creation", + "Redundant explicit type in array creation", + Description = "Redundant explicit type in array creation", Category = IssueCategories.RedundanciesInCode, Severity = Severity.Warning, AnalysisDisableKeyword = "RedundantExplicitArrayCreation")] diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/RedundanciesInCode/RedundantToStringCallForValueTypesIssue.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/RedundanciesInCode/RedundantToStringCallForValueTypesIssue.cs index 227bc6924..5c3b74d4f 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/RedundanciesInCode/RedundantToStringCallForValueTypesIssue.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Synced/RedundanciesInCode/RedundantToStringCallForValueTypesIssue.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring { [IssueDescription("Redundant 'object.ToString()' call for value types", - Description = "Finds calls to ToString() which would be generated automatically by the compiler.", + Description = "Finds value type calls to ToString() which would be generated automatically by the compiler.", Category = IssueCategories.RedundanciesInCode, Severity = Severity.Hint)] public class RedundantToStringCallForValueTypesIssue : GatherVisitorCodeIssueProvider diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/AutoAsyncIssue.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/AutoAsyncIssue.cs index 20d360ed0..426537fc5 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/AutoAsyncIssue.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/AutoAsyncIssue.cs @@ -473,27 +473,28 @@ void TransformBody(List validInvocations, bool isVoid, boo var awaitedExpression = new UnaryOperatorExpression(UnaryOperatorType.Await, target); var replacements = new List(); var lambdaExpression = originalInvocation.Arguments.First(); - var continuationLambdaResolveResult = ctx.Resolve(lambdaExpression) as LambdaResolveResult; + var continuationLambdaResolveResult = (LambdaResolveResult) ctx.Resolve(lambdaExpression); - if (!continuationLambdaResolveResult.HasParameterList || - !continuationLambdaResolveResult.Parameters.First().Type.IsParameterized) + if (!continuationLambdaResolveResult.HasParameterList) { - //Either precedent is of type Task or the result is not used + //Lambda has no parameter, so creating a variable for the argument is not needed + // (since you can't use an argument that doesn't exist). replacements.Add(new ExpressionStatement(awaitedExpression)); } else { - //Precedent is of type Task - var lambdaParameter = continuationLambdaResolveResult.Parameters.First(); - var precedentTaskType = lambdaParameter.Type; - var precedentResultType = precedentTaskType.TypeArguments.First(); + //Lambda has a parameter, which can either be a Task or a Task. - //We might need to separate the task creation and awaiting - var taskIdentifiers = lambdaExpression.Descendants.OfType().Where(identifier => { + var lambdaParameter = continuationLambdaResolveResult.Parameters[0]; + bool isTaskIdentifierUsed = lambdaExpression.Descendants.OfType().Any(identifier => { if (identifier.Identifier != lambdaParameter.Name) return false; var identifierMre = identifier.Parent as MemberReferenceExpression; return identifierMre == null || identifierMre.MemberName != "Result"; }); - if (taskIdentifiers.Any()) { + + var precedentTaskType = lambdaParameter.Type; + + //We might need to separate the task creation and awaiting + if (isTaskIdentifierUsed) { //Create new task variable var taskExpression = awaitedExpression.Expression; taskExpression.Detach(); @@ -503,7 +504,14 @@ void TransformBody(List validInvocations, bool isVoid, boo awaitedExpression.Expression = new IdentifierExpression(effectiveTaskName); } - replacements.Add(new VariableDeclarationStatement(CreateShortType(originalInvocation, precedentResultType), resultName, awaitedExpression)); + if (precedentTaskType.IsParameterized) { + //precedent is Task + var precedentResultType = precedentTaskType.TypeArguments.First(); + replacements.Add(new VariableDeclarationStatement(CreateShortType(originalInvocation, precedentResultType), resultName, awaitedExpression)); + } else { + //precedent is Task + replacements.Add(awaitedExpression); + } } var parentStatement = continuation.GetParent(); diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/MethodOverloadHidesOptionalParameterIssue.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/MethodOverloadHidesOptionalParameterIssue.cs deleted file mode 100644 index 1c7469a48..000000000 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/MethodOverloadHidesOptionalParameterIssue.cs +++ /dev/null @@ -1,85 +0,0 @@ -// -// MethodOverloadHidesOptionalParameterIssue.cs -// -// Author: -// Mansheng Yang -// -// Copyright (c) 2012 Mansheng Yang -// -// 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.Collections.Generic; -using System.Linq; -using ICSharpCode.NRefactory.Semantics; -using ICSharpCode.NRefactory.TypeSystem; -using ICSharpCode.NRefactory.Refactoring; - -namespace ICSharpCode.NRefactory.CSharp.Refactoring -{ - [IssueDescription("Method with optional parameter is hidden by overload", - Description = "Method with optional parameter is hidden by overload", - Category = IssueCategories.CodeQualityIssues, - Severity = Severity.Warning, - AnalysisDisableKeyword = "MethodOverloadWithOptionalParameter")] - public class MethodOverloadHidesOptionalParameterIssue : GatherVisitorCodeIssueProvider - { - protected override IGatherVisitor CreateVisitor(BaseRefactoringContext context) - { - return new GatherVisitor(context); - } - - class GatherVisitor : GatherVisitorBase - { - public GatherVisitor(BaseRefactoringContext ctx) - : base(ctx) - { - } - - public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration) - { - base.VisitMethodDeclaration(methodDeclaration); - - var resolveResult = ctx.Resolve(methodDeclaration) as MemberResolveResult; - if (resolveResult == null) - return; - var method = resolveResult.Member as IMethod; - if (method == null) - return; - - if (method.Parameters.Count == 0 || !method.Parameters.Last().IsOptional) - return; - - var overloads = method.DeclaringType.GetMethods(m => m.Name == method.Name && m.TypeParameters.Count == method.TypeParameters.Count) - .ToArray(); - - var parameterNodes = methodDeclaration.Parameters.ToArray(); - var parameters = new List(); - for (int i = 0; i < method.Parameters.Count; i++) { - if (method.Parameters[i].IsOptional && - overloads.Any(m => ParameterListComparer.Instance.Equals(parameters, m.Parameters))) { - AddIssue(new CodeIssue(parameterNodes[i].StartLocation, parameterNodes.Last().EndLocation, - ctx.TranslateString("Method with optional parameter is hidden by overload"))); - break; - } - parameters.Add(method.Parameters[i]); - } - } - } - } -} diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/ICSharpCode.NRefactory.CSharp.Refactoring.csproj b/ICSharpCode.NRefactory.CSharp.Refactoring/ICSharpCode.NRefactory.CSharp.Refactoring.csproj index f5d2ae1d5..0f70e17b1 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/ICSharpCode.NRefactory.CSharp.Refactoring.csproj +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/ICSharpCode.NRefactory.CSharp.Refactoring.csproj @@ -314,7 +314,6 @@ - diff --git a/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs b/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs index eb89e5916..d5294f99b 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs @@ -411,6 +411,8 @@ public void AddChild (T child, Role role) where T : AstNode if (child == null || child.IsNull) return; ThrowIfFrozen(); + if (child == this) + throw new ArgumentException ("Cannot add a node to itself as a child.", "child"); if (child.parent != null) throw new ArgumentException ("Node is already used in another tree.", "child"); if (child.IsFrozen) @@ -418,6 +420,20 @@ public void AddChild (T child, Role role) where T : AstNode AddChildUnsafe (child, role); } + public void AddChildWithExistingRole (AstNode child) + { + if (child == null || child.IsNull) + return; + ThrowIfFrozen(); + if (child == this) + throw new ArgumentException ("Cannot add a node to itself as a child.", "child"); + if (child.parent != null) + throw new ArgumentException ("Node is already used in another tree.", "child"); + if (child.IsFrozen) + throw new ArgumentException ("Cannot add a frozen node.", "child"); + AddChildUnsafe (child, child.Role); + } + /// /// Adds a child without performing any safety checks. /// diff --git a/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs b/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs index ddcd3ff3a..1a46006f2 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs @@ -169,5 +169,50 @@ public static int GetModifierLength(Modifiers modifier) throw new NotSupportedException("Invalid value for Modifiers"); } } + + public static Modifiers GetModifierValue(string modifier) + { + switch (modifier) { + case "private": + return Modifiers.Private; + case "internal": + return Modifiers.Internal; + case "protected": + return Modifiers.Protected; + case "public": + return Modifiers.Public; + case "abstract": + return Modifiers.Abstract; + case "virtual": + return Modifiers.Virtual; + case "sealed": + return Modifiers.Sealed; + case "static": + return Modifiers.Static; + case "override": + return Modifiers.Override; + case "readonly": + return Modifiers.Readonly; + case "const": + return Modifiers.Const; + case "new": + return Modifiers.New; + case "partial": + return Modifiers.Partial; + case "extern": + return Modifiers.Extern; + case "volatile": + return Modifiers.Volatile; + case "unsafe": + return Modifiers.Unsafe; + case "async": + return Modifiers.Async; + case "any": + // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST + return Modifiers.Any; + default: + throw new NotSupportedException("Invalid value for Modifiers"); + } + } } } \ No newline at end of file diff --git a/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs b/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs index a7a3ab7b5..fbfeb6f91 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs @@ -1,6 +1,6 @@ // // NullReferenceExpression.cs -// +// // Author: // Mike Krüger // @@ -38,6 +38,12 @@ public override TextLocation StartLocation { } } + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.location = value; + } + public override TextLocation EndLocation { get { return new TextLocation (location.Line, location.Column + "null".Length); @@ -57,7 +63,7 @@ public override void AcceptVisitor (IAstVisitor visitor) { visitor.VisitNullReferenceExpression (this); } - + public override T AcceptVisitor (IAstVisitor visitor) { return visitor.VisitNullReferenceExpression (this); diff --git a/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs b/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs index e9e9ea620..adfe7c3ba 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs @@ -1,6 +1,6 @@ // // PrimitiveExpression.cs -// +// // Author: // Mike Krüger // @@ -42,13 +42,20 @@ public override TextLocation StartLocation { } } + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.startLocation = value; + this.endLocation = null; + } + string literalValue; TextLocation? endLocation; public override TextLocation EndLocation { get { if (!endLocation.HasValue) { - endLocation = value is string ? AdvanceLocation (StartLocation, literalValue) : - new TextLocation (StartLocation.Line, StartLocation.Column + literalValue.Length); + endLocation = value is string ? AdvanceLocation (StartLocation, literalValue ?? "") : + new TextLocation (StartLocation.Line, StartLocation.Column + (literalValue ?? "").Length); } return endLocation.Value; } @@ -58,46 +65,56 @@ public override TextLocation EndLocation { public object Value { get { return this.value; } - set { - ThrowIfFrozen(); + set { + ThrowIfFrozen(); this.value = value; + literalValue = null; } } + /// Never returns null. public string LiteralValue { + get { return literalValue ?? ""; } + } + + /// Can be null. + public string UnsafeLiteralValue { get { return literalValue; } - set { - if (value == null) - throw new ArgumentNullException(); - ThrowIfFrozen(); - literalValue = value; - } + } + + public void SetValue(object value, string literalValue) + { + if (value == null) + throw new ArgumentNullException(); + ThrowIfFrozen(); + this.value = value; + this.literalValue = literalValue; } public PrimitiveExpression (object value) { this.Value = value; - this.literalValue = ""; + this.literalValue = null; } public PrimitiveExpression (object value, string literalValue) { this.Value = value; - this.literalValue = literalValue ?? ""; + this.literalValue = literalValue; } public PrimitiveExpression (object value, TextLocation startLocation, string literalValue) { this.Value = value; this.startLocation = startLocation; - this.literalValue = literalValue ?? ""; + this.literalValue = literalValue; } public override void AcceptVisitor (IAstVisitor visitor) { visitor.VisitPrimitiveExpression (this); } - + public override T AcceptVisitor (IAstVisitor visitor) { return visitor.VisitPrimitiveExpression (this); diff --git a/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs b/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs index fad153b03..3244e746e 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs @@ -83,6 +83,12 @@ public override TextLocation StartLocation { } } + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.startLocation = value; + } + const uint verbatimBit = 1u << AstNodeFlagsUsedBits; public bool IsVerbatim { diff --git a/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs b/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs index 8861e32ee..5b52a37ac 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs @@ -71,6 +71,13 @@ public override TextLocation StartLocation { return location; } } + + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.location = value; + } + public override TextLocation EndLocation { get { return new TextLocation (location.Line, location.Column + keyword.Length); diff --git a/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs b/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs index c36fb5fb1..3f688aa0d 100644 --- a/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs +++ b/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs @@ -983,6 +983,7 @@ IEnumerable MagicKeyCompletion(char completionChar, bool contro if (!(char.IsLetter(completionChar) || completionChar == '_') && (!controlSpace || identifierStart == null)) { return controlSpace ? HandleAccessorContext() ?? DefaultControlSpaceItems(identifierStart) : null; } + if (identifierStart != null) { if (identifierStart.Node is TypeParameterDeclaration) { return null; @@ -1577,7 +1578,10 @@ IEnumerable DefaultControlSpaceItems(ExpressionResult xp = null return wrapper.Result; } } - + var pDecl = node as ParameterDeclaration; + if (pDecl != null && pDecl.Parent is LambdaExpression) { + return null; + } /* if (Unit != null && (node == null || node is TypeDeclaration)) { var constructor = Unit.GetNodeAt( location.Line, @@ -3277,10 +3281,7 @@ ExpressionResult GetExpressionAtCursor() if (expr == null) expr = baseUnit.GetNodeAt(location.Line, location.Column - 1); // try insertStatement - if (expr == null && baseUnit.GetNodeAt( - location.Line, - location.Column - ) != null) { + if (expr == null && baseUnit.GetNodeAt(location.Line, location.Column) != null) { tmpUnit = baseUnit = ParseStub("a();", false); expr = baseUnit.GetNodeAt( location.Line, @@ -3382,9 +3383,17 @@ ExpressionResult GetExpressionAtCursor() } } + // try lambda if (expr == null) { - return null; + baseUnit = ParseStub(") => {}", false); + expr = baseUnit.GetNodeAt( + location.Line, + location.Column + ); } + + if (expr == null) + return null; return new ExpressionResult(expr, baseUnit); } diff --git a/ICSharpCode.NRefactory.CSharp/Formatter/CSharpFormattingOptions.cs b/ICSharpCode.NRefactory.CSharp/Formatter/CSharpFormattingOptions.cs index 458531c7c..9ee084422 100644 --- a/ICSharpCode.NRefactory.CSharp/Formatter/CSharpFormattingOptions.cs +++ b/ICSharpCode.NRefactory.CSharp/Formatter/CSharpFormattingOptions.cs @@ -160,6 +160,13 @@ public bool AlignEmbeddedIfStatements { // tested set; } + public bool AlignElseInIfStatements { + get; + set; + } + + + public PropertyFormatting AutoPropertyFormatting { // tested get; set; diff --git a/ICSharpCode.NRefactory.CSharp/Formatter/FormattingVisitor_Global.cs b/ICSharpCode.NRefactory.CSharp/Formatter/FormattingVisitor_Global.cs index a8720aa0a..09f4c4142 100644 --- a/ICSharpCode.NRefactory.CSharp/Formatter/FormattingVisitor_Global.cs +++ b/ICSharpCode.NRefactory.CSharp/Formatter/FormattingVisitor_Global.cs @@ -256,17 +256,24 @@ int GetTypeLevelNewLinesFor(AstNode child) var nextSibling = child.GetNextSibling(NoWhitespacePredicate); if (child is PreProcessorDirective) { var directive = (PreProcessorDirective)child; - if (directive.Type == PreProcessorDirectiveType.Region) + if (directive.Type == PreProcessorDirectiveType.Region) { blankLines += policy.BlankLinesInsideRegion; - if (directive.Type == PreProcessorDirectiveType.Endregion) + } + if (directive.Type == PreProcessorDirectiveType.Endregion) { + if (child.GetNextSibling(NoWhitespacePredicate) is CSharpTokenNode) + return 1; blankLines += policy.BlankLinesAroundRegion; + } return blankLines; } if (nextSibling is PreProcessorDirective) { var directive = (PreProcessorDirective)nextSibling; - if (directive.Type == PreProcessorDirectiveType.Region) + if (directive.Type == PreProcessorDirectiveType.Region) { + if (child is CSharpTokenNode) + return 1; blankLines += policy.BlankLinesAroundRegion; + } if (directive.Type == PreProcessorDirectiveType.Endregion) blankLines += policy.BlankLinesInsideRegion; if (directive.Type == PreProcessorDirectiveType.Endif) diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index 85d1001fb..6fe08f006 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -209,9 +209,12 @@ + - + + + diff --git a/ICSharpCode.NRefactory.CSharp/IndentEngine/IndentState.cs b/ICSharpCode.NRefactory.CSharp/IndentEngine/IndentState.cs index 47dfb46eb..968f6d3ff 100644 --- a/ICSharpCode.NRefactory.CSharp/IndentEngine/IndentState.cs +++ b/ICSharpCode.NRefactory.CSharp/IndentEngine/IndentState.cs @@ -210,6 +210,21 @@ public virtual void Push(char ch) public virtual void CheckKeyword(string keyword) { } + /// + /// When derived, checks if the given sequence of chars form + /// a valid keyword or variable name, depending on the state. + /// + /// + /// A possible keyword. + /// + /// + /// This method should be called from . + /// It is left to derived classes to call this method because of + /// performance issues. + /// + public virtual void CheckKeywordOnPush(string keyword) + { } + #endregion } @@ -478,7 +493,8 @@ public override char ClosedBracket public BracesBodyState(CSharpIndentEngine engine, IndentState parent = null) : base(engine, parent) - { } + { + } public BracesBodyState(BracesBodyState prototype, CSharpIndentEngine engine) : base(prototype, engine) @@ -556,6 +572,11 @@ public override void Push(char ch) ThisLineIndent.Push(IndentType.Continuation); } + if (Engine.wordToken.ToString() == "else") + { + CheckKeywordOnPush("else"); + } + base.Push(ch); } @@ -621,6 +642,7 @@ public virtual void OnStatementExit() NextLineIndent.PopWhile(IndentType.Continuation); CurrentStatement = Statement.None; + NextBody = Body.None; LastBlockIndent = null; } @@ -677,7 +699,7 @@ public enum Statement static readonly Dictionary statements = new Dictionary { { "if", Statement.If }, - { "else", Statement.Else }, + // { "else", Statement.Else }, // should be handled in CheckKeywordAtPush { "do", Statement.Do }, { "while", Statement.While }, { "for", Statement.For }, @@ -718,6 +740,39 @@ public enum Statement "struct" }; + /// + /// Checks if the given string is a keyword and sets the + /// and the + /// variables appropriately. + /// + /// + /// A possible keyword. + /// + /// + /// This method is called from + /// + public override void CheckKeywordOnPush(string keyword) + { + if (keyword == "else") + { + CurrentStatement = Statement.Else; + + // OPTION: CSharpFormattingOptions.AlignElseInIfStatements + if (!Engine.formattingOptions.AlignElseInIfStatements && NestedIfStatementLevels.Count > 0) + { + ThisLineIndent = NestedIfStatementLevels.Pop().Clone(); + NextLineIndent = ThisLineIndent.Clone(); + } + + NextLineIndent.Push(IndentType.Continuation); + } + + if (blocks.Contains(keyword) && Engine.NeedsReindent) + { + LastBlockIndent = Indent.ConvertFrom(Engine.CurrentIndent, ThisLineIndent, Engine.textEditorOptions); + } + } + /// /// Checks if the given string is a keyword and sets the /// and the @@ -778,29 +833,15 @@ public override void CheckKeyword(string keyword) NextLineIndent.PopIf(IndentType.Continuation); } - // else statement is handled differently - if (CurrentStatement == Statement.Else) + // only add continuation for 'else' in 'else if' statement. + if (!(CurrentStatement == Statement.If && previousStatement == Statement.Else && !Engine.isLineStartBeforeWordToken)) { - if (NestedIfStatementLevels.Count > 0) - { - ThisLineIndent = NestedIfStatementLevels.Pop().Clone(); - NextLineIndent = ThisLineIndent.Clone(); - } - NextLineIndent.Push(IndentType.Continuation); } - else - { - // only add continuation for 'else' in 'else if' statement. - if (!(CurrentStatement == Statement.If && previousStatement == Statement.Else && !Engine.isLineStartBeforeWordToken)) - { - NextLineIndent.Push(IndentType.Continuation); - } - if (CurrentStatement == Statement.If) - { - NestedIfStatementLevels.Push(ThisLineIndent); - } + if (CurrentStatement == Statement.If) + { + NestedIfStatementLevels.Push(ThisLineIndent); } } @@ -1080,9 +1121,11 @@ public override void Push(char ch) { if (ch == Engine.newLineChar) { - if (NextLineIndent.PopIf(IndentType.Continuation)) - { - NextLineIndent.Push(IndentType.Block); + if (Engine.formattingOptions.AnonymousMethodBraceStyle == BraceStyle.EndOfLine || + Engine.formattingOptions.AnonymousMethodBraceStyle == BraceStyle.EndOfLineWithoutSpace) { + if (NextLineIndent.PopIf(IndentType.Continuation)) { + NextLineIndent.Push(IndentType.Block); + } } } else if (!IsSomethingPushed) @@ -1264,7 +1307,7 @@ public override void Push(char ch) // happen, we check for "endregion" on every push. if (Engine.wordToken.ToString() == "endregion") { - ThisLineIndent = Parent.NextLineIndent.Clone(); + CheckKeywordOnPush("endregion"); } base.Push(ch); @@ -1367,7 +1410,7 @@ public override void InitializeState() { "else", PreProcessorDirective.Else }, { "endif", PreProcessorDirective.Endif }, { "region", PreProcessorDirective.Region }, - { "endregion", PreProcessorDirective.Region }, + { "endregion", PreProcessorDirective.Endregion }, { "pragma", PreProcessorDirective.Pragma }, { "warning", PreProcessorDirective.Warning }, { "error", PreProcessorDirective.Error }, @@ -1376,6 +1419,15 @@ public override void InitializeState() { "undef", PreProcessorDirective.Undef } }; + public override void CheckKeywordOnPush(string keyword) + { + if (keyword == "endregion") + { + DirectiveType = PreProcessorDirective.Endregion; + ThisLineIndent = Parent.NextLineIndent.Clone(); + } + } + public override void CheckKeyword(string keyword) { // check if the directive type has already been set @@ -1388,7 +1440,7 @@ public override void CheckKeyword(string keyword) { DirectiveType = preProcessorDirectives[keyword]; - // adjust the indentation for the region/endregion directives + // adjust the indentation for the region directive if (DirectiveType == PreProcessorDirective.Region) { ThisLineIndent = Parent.NextLineIndent.Clone(); @@ -1412,7 +1464,7 @@ public enum PreProcessorDirective Else, Endif, Region, - // EndRegion, // use Region instead + Endregion, Pragma, Warning, Error, @@ -1697,8 +1749,8 @@ public class LineCommentState : IndentState public LineCommentState(CSharpIndentEngine engine, IndentState parent = null) : base(engine, parent) { - if (engine.formattingOptions.KeepCommentsAtFirstColumn && engine.column == 2) - ThisLineIndent.Reset(); + /* if (engine.formattingOptions.KeepCommentsAtFirstColumn && engine.column == 2) + ThisLineIndent.Reset();*/ } public LineCommentState(LineCommentState prototype, CSharpIndentEngine engine) diff --git a/ICSharpCode.NRefactory.CSharp/IndentEngine/TextPasteIndentEngine.cs b/ICSharpCode.NRefactory.CSharp/IndentEngine/TextPasteIndentEngine.cs index 2778395b8..9170b029f 100644 --- a/ICSharpCode.NRefactory.CSharp/IndentEngine/TextPasteIndentEngine.cs +++ b/ICSharpCode.NRefactory.CSharp/IndentEngine/TextPasteIndentEngine.cs @@ -171,6 +171,7 @@ string ITextPasteHandler.FormatPlainText(int offset, string text, byte[] copyDat pasteAtLineStart = false; indentedText.Append(curLine); curLine.Length = 0; + gotNewLine = false; continue; } } @@ -440,25 +441,10 @@ protected StringLiteralPasteStrategy() #endregion - Dictionary> encodeReplace = new Dictionary> { - { '\"', "\\\"" }, - { '\\', "\\\\" }, - { '\n', "\\n" }, - { '\r', "\\r" }, - { '\t', "\\t" }, - }; - Dictionary decodeReplace = new Dictionary { - { '"', '"' }, - { '\\', '\\' }, - { 'n', '\n' }, - { 'r', '\r' }, - { 't', '\t' }, - }; - /// public string Encode(string text) { - return string.Concat(text.SelectMany(c => encodeReplace.ContainsKey(c) ? encodeReplace [c] : new[] { c })); + return CSharpOutputVisitor.ConvertString(text); } /// @@ -466,24 +452,118 @@ public string Decode(string text) { var result = new StringBuilder(); bool isEscaped = false; - - foreach (var ch in text) { + + for (int i = 0; i < text.Length; i++) { + var ch = text[i]; if (isEscaped) { - if (decodeReplace.ContainsKey(ch)) { - result.Append(decodeReplace [ch]); - } else { - result.Append('\\', ch); + switch (ch) { + case 'a': + result.Append('\a'); + break; + case 'b': + result.Append('\b'); + break; + case 'n': + result.Append('\n'); + break; + case 't': + result.Append('\t'); + break; + case 'v': + result.Append('\v'); + break; + case 'r': + result.Append('\r'); + break; + case '\\': + result.Append('\\'); + break; + case 'f': + result.Append('\f'); + break; + case '0': + result.Append(0); + break; + case '"': + result.Append('"'); + break; + case '\'': + result.Append('\''); + break; + case 'x': + char r; + if (TryGetHex(text, -1, ref i, out r)) { + result.Append(r); + break; + } + goto default; + case 'u': + if (TryGetHex(text, 4, ref i, out r)) { + result.Append(r); + break; + } + goto default; + case 'U': + if (TryGetHex(text, 8, ref i, out r)) { + result.Append(r); + break; + } + goto default; + default: + result.Append('\\'); + result.Append(ch); + break; } - } else if (ch != '\\') { + isEscaped = false; + continue; + } + if (ch != '\\') { result.Append(ch); } - - isEscaped = !isEscaped && ch == '\\'; + else { + isEscaped = true; + } } - + return result.ToString(); } + static bool TryGetHex(string text, int count, ref int idx, out char r) + { + int i; + int total = 0; + int top = count != -1 ? count : 4; + + for (i = 0; i < top; i++) { + int c = text[idx + 1 + i]; + + if (c >= '0' && c <= '9') + c = (int) c - (int) '0'; + else if (c >= 'A' && c <= 'F') + c = (int) c - (int) 'A' + 10; + else if (c >= 'a' && c <= 'f') + c = (int) c - (int) 'a' + 10; + else { + r = '\0'; + return false; + } + total = (total * 16) + c; + } + + if (top == 8) { + if (total > 0x0010FFFF) { + r = '\0'; + return false; + } + + if (total >= 0x00010000) + total = ((total - 0x00010000) / 0x0400 + 0xD800); + } + r = (char)total; + idx += top; + return true; + } + /// public PasteStrategy Type { get { return PasteStrategy.StringLiteral; } @@ -513,7 +593,7 @@ protected VerbatimStringPasteStrategy() #endregion - Dictionary> encodeReplace = new Dictionary> { + static readonly Dictionary> encodeReplace = new Dictionary> { { '\"', "\"\"" }, }; diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs index 7a94b2e40..84d72ff75 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs @@ -37,98 +37,98 @@ public string ConvertEntity(IEntity entity) throw new ArgumentNullException("entity"); StringWriter writer = new StringWriter(); - ConvertEntity(entity, new TextWriterOutputFormatter(writer), FormattingOptionsFactory.CreateMono ()); + ConvertEntity(entity, new TextWriterTokenWriter(writer), FormattingOptionsFactory.CreateMono ()); return writer.ToString(); } - public void ConvertEntity(IEntity entity, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public void ConvertEntity(IEntity entity, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { if (entity == null) throw new ArgumentNullException("entity"); - if (formatter == null) - throw new ArgumentNullException("formatter"); + if (writer == null) + throw new ArgumentNullException("writer"); if (formattingPolicy == null) throw new ArgumentNullException("options"); TypeSystemAstBuilder astBuilder = CreateAstBuilder(); EntityDeclaration node = astBuilder.ConvertEntity(entity); - PrintModifiers(node.Modifiers, formatter); + PrintModifiers(node.Modifiers, writer); if ((ConversionFlags & ConversionFlags.ShowDefinitionKeyword) == ConversionFlags.ShowDefinitionKeyword) { if (node is TypeDeclaration) { switch (((TypeDeclaration)node).ClassType) { case ClassType.Class: - formatter.WriteKeyword("class"); + writer.WriteKeyword(Roles.ClassKeyword, "class"); break; case ClassType.Struct: - formatter.WriteKeyword("struct"); + writer.WriteKeyword(Roles.StructKeyword, "struct"); break; case ClassType.Interface: - formatter.WriteKeyword("interface"); + writer.WriteKeyword(Roles.InterfaceKeyword, "interface"); break; case ClassType.Enum: - formatter.WriteKeyword("enum"); + writer.WriteKeyword(Roles.EnumKeyword, "enum"); break; default: throw new Exception("Invalid value for ClassType"); } - formatter.Space(); + writer.Space(); } else if (node is DelegateDeclaration) { - formatter.WriteKeyword("delegate"); - formatter.Space(); + writer.WriteKeyword(Roles.DelegateKeyword, "delegate"); + writer.Space(); } else if (node is EventDeclaration) { - formatter.WriteKeyword("event"); - formatter.Space(); + writer.WriteKeyword(EventDeclaration.EventKeywordRole, "event"); + writer.Space(); } } if ((ConversionFlags & ConversionFlags.ShowReturnType) == ConversionFlags.ShowReturnType) { var rt = node.GetChildByRole(Roles.Type); if (!rt.IsNull) { - rt.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); - formatter.Space(); + rt.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); + writer.Space(); } } if (entity is ITypeDefinition) - WriteTypeDeclarationName((ITypeDefinition)entity, formatter, formattingPolicy); + WriteTypeDeclarationName((ITypeDefinition)entity, writer, formattingPolicy); else - WriteMemberDeclarationName((IMember)entity, formatter, formattingPolicy); + WriteMemberDeclarationName((IMember)entity, writer, formattingPolicy); if ((ConversionFlags & ConversionFlags.ShowParameterList) == ConversionFlags.ShowParameterList && HasParameters(entity)) { - formatter.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? "[" : "("); + writer.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? Roles.LBracket : Roles.LPar, entity.SymbolKind == SymbolKind.Indexer ? "[" : "("); bool first = true; foreach (var param in node.GetChildrenByRole(Roles.Parameter)) { if (first) { first = false; } else { - formatter.WriteToken(","); - formatter.Space(); + writer.WriteToken(Roles.Comma, ","); + writer.Space(); } - param.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); + param.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); } - formatter.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? "]" : ")"); + writer.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? Roles.RBracket : Roles.RPar, entity.SymbolKind == SymbolKind.Indexer ? "]" : ")"); } if ((ConversionFlags & ConversionFlags.ShowBody) == ConversionFlags.ShowBody && !(node is TypeDeclaration)) { IProperty property = entity as IProperty; if (property != null) { - formatter.Space(); - formatter.WriteToken("{"); - formatter.Space(); + writer.Space(); + writer.WriteToken(Roles.LBrace, "{"); + writer.Space(); if (property.CanGet) { - formatter.WriteKeyword("get"); - formatter.WriteToken(";"); - formatter.Space(); + writer.WriteKeyword(PropertyDeclaration.GetKeywordRole, "get"); + writer.WriteToken(Roles.Semicolon, ";"); + writer.Space(); } if (property.CanSet) { - formatter.WriteKeyword("set"); - formatter.WriteToken(";"); - formatter.Space(); + writer.WriteKeyword(PropertyDeclaration.SetKeywordRole, "set"); + writer.WriteToken(Roles.Semicolon, ";"); + writer.Space(); } - formatter.WriteToken("}"); + writer.WriteToken(Roles.RBrace, "}"); } else { - formatter.WriteToken(";"); + writer.WriteToken(Roles.Semicolon, ";"); } } } @@ -160,87 +160,96 @@ TypeSystemAstBuilder CreateAstBuilder() return astBuilder; } - void WriteTypeDeclarationName(ITypeDefinition typeDef, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + void WriteTypeDeclarationName(ITypeDefinition typeDef, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); + EntityDeclaration node = astBuilder.ConvertEntity(typeDef); if (typeDef.DeclaringTypeDefinition != null) { - WriteTypeDeclarationName(typeDef.DeclaringTypeDefinition, formatter, formattingPolicy); - formatter.WriteToken("."); + WriteTypeDeclarationName(typeDef.DeclaringTypeDefinition, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } else if ((ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) == ConversionFlags.UseFullyQualifiedTypeNames) { - formatter.WriteIdentifier(typeDef.Namespace); - formatter.WriteToken("."); + WriteQualifiedName(typeDef.Namespace, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } - formatter.WriteIdentifier(typeDef.Name); + writer.WriteIdentifier(node.NameToken); if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList) { - var outputVisitor = new CSharpOutputVisitor(formatter, formattingPolicy); - outputVisitor.WriteTypeParameters(astBuilder.ConvertEntity(typeDef).GetChildrenByRole(Roles.TypeParameter)); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + outputVisitor.WriteTypeParameters(node.GetChildrenByRole(Roles.TypeParameter)); } } - void WriteMemberDeclarationName(IMember member, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + void WriteMemberDeclarationName(IMember member, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); + EntityDeclaration node = astBuilder.ConvertEntity(member); if ((ConversionFlags & ConversionFlags.ShowDeclaringType) == ConversionFlags.ShowDeclaringType) { - ConvertType(member.DeclaringType, formatter, formattingPolicy); - formatter.WriteToken("."); + ConvertType(member.DeclaringType, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } switch (member.SymbolKind) { case SymbolKind.Indexer: - formatter.WriteKeyword("this"); + writer.WriteKeyword(Roles.Identifier, "this"); break; case SymbolKind.Constructor: - formatter.WriteIdentifier(member.DeclaringType.Name); + WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy); break; case SymbolKind.Destructor: - formatter.WriteToken("~"); - formatter.WriteIdentifier(member.DeclaringType.Name); + writer.WriteToken(DestructorDeclaration.TildeRole, "~"); + WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy); break; case SymbolKind.Operator: switch (member.Name) { case "op_Implicit": - formatter.WriteKeyword("implicit"); - formatter.Space(); - formatter.WriteKeyword("operator"); - formatter.Space(); - ConvertType(member.ReturnType, formatter, formattingPolicy); + writer.WriteKeyword(OperatorDeclaration.ImplicitRole, "implicit"); + writer.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); + ConvertType(member.ReturnType, writer, formattingPolicy); break; case "op_Explicit": - formatter.WriteKeyword("explicit"); - formatter.Space(); - formatter.WriteKeyword("operator"); - formatter.Space(); - ConvertType(member.ReturnType, formatter, formattingPolicy); + writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit"); + writer.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); + ConvertType(member.ReturnType, writer, formattingPolicy); break; default: - formatter.WriteKeyword("operator"); - formatter.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); var operatorType = OperatorDeclaration.GetOperatorType(member.Name); if (operatorType.HasValue) - formatter.WriteToken(OperatorDeclaration.GetToken(operatorType.Value)); + writer.WriteToken(OperatorDeclaration.GetRole(operatorType.Value), OperatorDeclaration.GetToken(operatorType.Value)); else - formatter.WriteIdentifier(member.Name); + writer.WriteIdentifier(node.NameToken); break; } break; default: - formatter.WriteIdentifier(member.Name); + writer.WriteIdentifier(Identifier.Create(member.Name)); break; } if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList && member.SymbolKind == SymbolKind.Method) { - var outputVisitor = new CSharpOutputVisitor(formatter, formattingPolicy); - outputVisitor.WriteTypeParameters(astBuilder.ConvertEntity(member).GetChildrenByRole(Roles.TypeParameter)); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + outputVisitor.WriteTypeParameters(node.GetChildrenByRole(Roles.TypeParameter)); } } - void PrintModifiers(Modifiers modifiers, IOutputFormatter formatter) + void PrintModifiers(Modifiers modifiers, TokenWriter writer) { foreach (var m in CSharpModifierToken.AllModifiers) { if ((modifiers & m) == m) { - formatter.WriteKeyword(CSharpModifierToken.GetModifierName(m)); - formatter.Space(); + writer.WriteKeyword(EntityDeclaration.ModifierRole, CSharpModifierToken.GetModifierName(m)); + writer.Space(); } } } + + void WriteQualifiedName(string name, TokenWriter writer, CSharpFormattingOptions formattingPolicy) + { + var node = AstType.Create(name); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + node.AcceptVisitor(outputVisitor); + } #endregion public string ConvertVariable(IVariable v) @@ -260,16 +269,16 @@ public string ConvertType(IType type) return astType.ToString(); } - public void ConvertType(IType type, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public void ConvertType(IType type, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); AstType astType = astBuilder.ConvertType(type); - astType.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); + astType.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); } public string ConvertConstantValue(object constantValue) { - return CSharpOutputVisitor.PrintPrimitiveValue(constantValue); + return TextWriterTokenWriter.PrintPrimitiveValue(constantValue); } public string WrapComment(string comment) diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs index 927082dc8..5e36d4659 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -25,6 +25,7 @@ using System.Threading.Tasks; using ICSharpCode.NRefactory.PatternMatching; using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.CSharp; namespace ICSharpCode.NRefactory.CSharp { @@ -33,27 +34,9 @@ namespace ICSharpCode.NRefactory.CSharp /// public class CSharpOutputVisitor : IAstVisitor { - readonly IOutputFormatter formatter; + readonly TokenWriter writer; readonly CSharpFormattingOptions policy; readonly Stack containerStack = new Stack (); - readonly Stack positionStack = new Stack (); - - /// - /// Used to insert the minimal amount of spaces so that the lexer recognizes the tokens that were written. - /// - LastWritten lastWritten; - - enum LastWritten - { - Whitespace, - Other, - KeywordOrIdentifier, - Plus, - Minus, - Ampersand, - QuestionMark, - Division - } public CSharpOutputVisitor (TextWriter textWriter, CSharpFormattingOptions formattingPolicy) { @@ -63,19 +46,19 @@ public CSharpOutputVisitor (TextWriter textWriter, CSharpFormattingOptions forma if (formattingPolicy == null) { throw new ArgumentNullException ("formattingPolicy"); } - this.formatter = new TextWriterOutputFormatter (textWriter); + this.writer = TokenWriter.Create(textWriter); this.policy = formattingPolicy; } - public CSharpOutputVisitor (IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public CSharpOutputVisitor (TokenWriter writer, CSharpFormattingOptions formattingPolicy) { - if (formatter == null) { - throw new ArgumentNullException ("formatter"); + if (writer == null) { + throw new ArgumentNullException ("writer"); } if (formattingPolicy == null) { throw new ArgumentNullException ("formattingPolicy"); } - this.formatter = formatter; + this.writer = new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(writer)); this.policy = formattingPolicy; } @@ -85,84 +68,15 @@ void StartNode(AstNode node) // Ensure that nodes are visited in the proper nested order. // Jumps to different subtrees are allowed only for the child of a placeholder node. Debug.Assert(containerStack.Count == 0 || node.Parent == containerStack.Peek() || containerStack.Peek().NodeType == NodeType.Pattern); - if (positionStack.Count > 0) { - WriteSpecialsUpToNode(node); - } containerStack.Push(node); - positionStack.Push(node.FirstChild); - formatter.StartNode(node); + writer.StartNode(node); } void EndNode(AstNode node) { Debug.Assert(node == containerStack.Peek()); - AstNode pos = positionStack.Pop(); - Debug.Assert(pos == null || pos.Parent == node); - WriteSpecials(pos, null); containerStack.Pop(); - formatter.EndNode(node); - } - #endregion - - #region WriteSpecials - /// - /// Writes all specials from start to end (exclusive). Does not touch the positionStack. - /// - void WriteSpecials(AstNode start, AstNode end) - { - for (AstNode pos = start; pos != end; pos = pos.NextSibling) { - if (pos.Role == Roles.Comment || pos.Role == Roles.NewLine || pos.Role == Roles.PreProcessorDirective) { - pos.AcceptVisitor(this); - } - } - } - - /// - /// Writes all specials between the current position (in the positionStack) and the next - /// node with the specified role. Advances the current position. - /// - void WriteSpecialsUpToRole(Role role) - { - WriteSpecialsUpToRole(role, null); - } - - 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 == role) { - WriteSpecials(positionStack.Pop(), 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; - } - } - } - - /// - /// Writes all specials between the current position (in the positionStack) and the specified node. - /// Advances the current position. - /// - void WriteSpecialsUpToNode(AstNode node) - { - if (positionStack.Count == 0) { - return; - } - for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) { - if (pos == node) { - WriteSpecials(positionStack.Pop(), 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; - } - } + writer.EndNode(node); } #endregion @@ -174,11 +88,9 @@ void WriteSpecialsUpToNode(AstNode node) /// When set prevents printing a space after comma. void Comma(AstNode nextNode, bool noSpaceAfterComma = false) { - WriteSpecialsUpToRole(Roles.Comma, nextNode); Space(policy.SpaceBeforeBracketComma); // TODO: Comma policy has changed. - formatter.WriteToken(","); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Comma, ","); Space(!noSpaceAfterComma && policy.SpaceAfterBracketComma); // TODO: Comma policy has changed. } @@ -186,10 +98,9 @@ void Comma(AstNode nextNode, bool noSpaceAfterComma = false) /// /// Writes an optional comma, e.g. at the end of an enum declaration or in an array initializer /// - void OptionalComma() + void OptionalComma(AstNode pos) { // Look if there's a comma after the current node, and insert it if it exists. - AstNode pos = positionStack.Peek(); while (pos != null && pos.NodeType == NodeType.Whitespace) { pos = pos.NextSibling; } @@ -201,12 +112,11 @@ void OptionalComma() /// /// Writes an optional semicolon, e.g. at the end of a type or namespace declaration. /// - void OptionalSemicolon() + void OptionalSemicolon(AstNode pos) { // Look if there's a semicolon after the current node, and insert it if it exists. - AstNode pos = positionStack.Peek(); while (pos != null && pos.NodeType == NodeType.Whitespace) { - pos = pos.NextSibling; + pos = pos.PrevSibling; } if (pos != null && pos.Role == Roles.Semicolon) { Semicolon(); @@ -284,6 +194,8 @@ void WriteCommaSeparatedListInBrackets(IEnumerable list) #endregion #region Write tokens + bool isAtStartOfLine = true; + /// /// Writes a keyword, and all specials up to /// @@ -294,40 +206,20 @@ void WriteKeyword(TokenRole tokenRole) void WriteKeyword(string token, Role tokenRole = null) { - if (tokenRole != null) { - WriteSpecialsUpToRole(tokenRole); - } - if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - } - formatter.WriteKeyword(token); - lastWritten = LastWritten.KeywordOrIdentifier; + writer.WriteKeyword(tokenRole, token); + isAtStartOfLine = false; } -/* void WriteKeyword (string keyword, Role tokenRole) + void WriteIdentifier(Identifier identifier) { - WriteSpecialsUpToRole (tokenRole); - if (lastWritten == LastWritten.KeywordOrIdentifier) - formatter.Space (); - formatter.WriteKeyword (keyword); - lastWritten = LastWritten.KeywordOrIdentifier; - }*/ + writer.WriteIdentifier(identifier); + isAtStartOfLine = false; + } - void WriteIdentifier(string identifier, Role identifierRole = null) + void WriteIdentifier(string identifier) { - WriteSpecialsUpToRole(identifierRole ?? Roles.Identifier); - if (IsKeyword(identifier, containerStack.Peek())) { - if (lastWritten == LastWritten.KeywordOrIdentifier) { - Space(); - } - // this space is not strictly required, so we call Space() - formatter.WriteToken("@"); - } else if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - // this space is strictly required, so we directly call the formatter - } - formatter.WriteIdentifier(identifier); - lastWritten = LastWritten.KeywordOrIdentifier; + AstType.Create(identifier).AcceptVisitor(this); + isAtStartOfLine = false; } void WriteToken(TokenRole tokenRole) @@ -337,34 +229,8 @@ void WriteToken(TokenRole tokenRole) void WriteToken(string token, Role tokenRole) { - WriteSpecialsUpToRole(tokenRole); - // Avoid that two +, - or ? tokens are combined into a ++, -- or ?? token. - // Note that we don't need to handle tokens like = because there's no valid - // C# program that contains the single token twice in a row. - // (for +, - and &, this can happen with unary operators; - // for ?, this can happen in "a is int? ? b : c" or "a as int? ?? 0"; - // and for /, this can happen with "1/ *ptr" or "1/ //comment".) - if (lastWritten == LastWritten.Plus && token [0] == '+' - || lastWritten == LastWritten.Minus && token [0] == '-' - || lastWritten == LastWritten.Ampersand && token [0] == '&' - || lastWritten == LastWritten.QuestionMark && token [0] == '?' - || lastWritten == LastWritten.Division && token [0] == '*') { - formatter.Space(); - } - formatter.WriteToken(token); - if (token == "+") { - lastWritten = LastWritten.Plus; - } else if (token == "-") { - lastWritten = LastWritten.Minus; - } else if (token == "&") { - lastWritten = LastWritten.Ampersand; - } else if (token == "?") { - lastWritten = LastWritten.QuestionMark; - } else if (token == "/") { - lastWritten = LastWritten.Division; - } else { - lastWritten = LastWritten.Other; - } + writer.WriteToken(tokenRole, token); + isAtStartOfLine = false; } void LPar() @@ -396,29 +262,78 @@ void Semicolon() void Space(bool addSpace = true) { if (addSpace) { - formatter.Space(); - lastWritten = LastWritten.Whitespace; + writer.Space(); } } void NewLine() { - formatter.NewLine(); - lastWritten = LastWritten.Whitespace; + writer.NewLine(); + isAtStartOfLine = true; } void OpenBrace(BraceStyle style) { - WriteSpecialsUpToRole(Roles.LBrace); - formatter.OpenBrace(style); - lastWritten = LastWritten.Other; + switch (style) { + case BraceStyle.DoNotChange: + case BraceStyle.EndOfLine: + case BraceStyle.BannerStyle: + if (!isAtStartOfLine) + writer.Space(); + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.EndOfLineWithoutSpace: + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.NextLine: + if (!isAtStartOfLine) + NewLine(); + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.NextLineShifted: + NewLine(); + writer.Indent(); + writer.WriteToken(Roles.LBrace, "{"); + NewLine(); + return; + case BraceStyle.NextLineShifted2: + NewLine(); + writer.Indent(); + writer.WriteToken(Roles.LBrace, "{"); + break; + default: + throw new ArgumentOutOfRangeException (); + } + writer.Indent(); + NewLine(); } void CloseBrace(BraceStyle style) { - WriteSpecialsUpToRole(Roles.RBrace); - formatter.CloseBrace(style); - lastWritten = LastWritten.Other; + switch (style) { + case BraceStyle.DoNotChange: + case BraceStyle.EndOfLine: + case BraceStyle.EndOfLineWithoutSpace: + case BraceStyle.NextLine: + writer.Unindent(); + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + break; + case BraceStyle.BannerStyle: + case BraceStyle.NextLineShifted: + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + writer.Unindent(); + break; + case BraceStyle.NextLineShifted2: + writer.Unindent(); + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + writer.Unindent(); + break; + default: + throw new ArgumentOutOfRangeException(); + } } #endregion @@ -502,17 +417,10 @@ void WriteQualifiedIdentifier(IEnumerable identifiers) foreach (Identifier ident in identifiers) { if (first) { first = false; - if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - } } else { - WriteSpecialsUpToRole(Roles.Dot, ident); - formatter.WriteToken("."); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Dot, "."); } - WriteSpecialsUpToNode(ident); - formatter.WriteIdentifier(ident.Name); - lastWritten = LastWritten.KeywordOrIdentifier; + writer.WriteIdentifier(ident); } } @@ -527,9 +435,9 @@ void WriteEmbeddedStatement(Statement embeddedStatement) VisitBlockStatement(block); } else { NewLine(); - formatter.Indent(); + writer.Indent(); embeddedStatement.AcceptVisitor(this); - formatter.Unindent(); + writer.Unindent(); } } @@ -625,8 +533,8 @@ public void VisitArrayInitializerExpression(ArrayInitializerExpression arrayInit // The output visitor will output nested braces only if they are necessary, // or if the braces tokens exist in the AST. bool bracesAreOptional = arrayInitializerExpression.Elements.Count == 1 - && IsObjectOrCollectionInitializer(arrayInitializerExpression.Parent) - && !CanBeConfusedWithObjectInitializer(arrayInitializerExpression.Elements.Single()); + && IsObjectOrCollectionInitializer(arrayInitializerExpression.Parent) + && !CanBeConfusedWithObjectInitializer(arrayInitializerExpression.Elements.Single()); if (bracesAreOptional && arrayInitializerExpression.LBraceToken.IsNull) { arrayInitializerExpression.Elements.Single().AcceptVisitor(this); } else { @@ -667,6 +575,7 @@ void PrintInitializerElements(AstNodeCollection elements) } OpenBrace(style); bool isFirst = true; + AstNode last = null; foreach (AstNode node in elements) { if (isFirst) { isFirst = false; @@ -674,9 +583,11 @@ void PrintInitializerElements(AstNodeCollection elements) Comma(node, noSpaceAfterComma: true); NewLine(); } + last = node; node.AcceptVisitor(this); } - OptionalComma(); + if (last != null) + OptionalComma(last.NextSibling); NewLine(); CloseBrace(style); } @@ -843,7 +754,7 @@ public void VisitDirectionExpression(DirectionExpression directionExpression) public void VisitIdentifierExpression(IdentifierExpression identifierExpression) { StartNode(identifierExpression); - WriteIdentifier(identifierExpression.Identifier); + WriteIdentifier(identifierExpression.IdentifierToken); WriteTypeArguments(identifierExpression.TypeArguments); EndNode(identifierExpression); } @@ -909,7 +820,7 @@ public void VisitMemberReferenceExpression(MemberReferenceExpression memberRefer StartNode(memberReferenceExpression); memberReferenceExpression.Target.AcceptVisitor(this); WriteToken(Roles.Dot); - WriteIdentifier(memberReferenceExpression.MemberName); + WriteIdentifier(memberReferenceExpression.MemberNameToken); WriteTypeArguments(memberReferenceExpression.TypeArguments); EndNode(memberReferenceExpression); } @@ -917,7 +828,7 @@ public void VisitMemberReferenceExpression(MemberReferenceExpression memberRefer public void VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentExpression) { StartNode(namedArgumentExpression); - namedArgumentExpression.NameToken.AcceptVisitor(this); + WriteIdentifier(namedArgumentExpression.NameToken); WriteToken(Roles.Colon); Space(); namedArgumentExpression.Expression.AcceptVisitor(this); @@ -927,7 +838,7 @@ public void VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentEx public void VisitNamedExpression(NamedExpression namedExpression) { StartNode(namedExpression); - namedExpression.NameToken.AcceptVisitor(this); + WriteIdentifier(namedExpression.NameToken); Space(); WriteToken(Roles.Assign); Space(); @@ -938,7 +849,7 @@ public void VisitNamedExpression(NamedExpression namedExpression) public void VisitNullReferenceExpression(NullReferenceExpression nullReferenceExpression) { StartNode(nullReferenceExpression); - WriteKeyword("null", nullReferenceExpression.Role); + writer.WritePrimitiveValue(null); EndNode(nullReferenceExpression); } @@ -984,7 +895,7 @@ public void VisitPointerReferenceExpression(PointerReferenceExpression pointerRe StartNode(pointerReferenceExpression); pointerReferenceExpression.Target.AcceptVisitor(this); WriteToken(PointerReferenceExpression.ArrowRole); - WriteIdentifier(pointerReferenceExpression.MemberName); + WriteIdentifier(pointerReferenceExpression.MemberNameToken); WriteTypeArguments(pointerReferenceExpression.TypeArguments); EndNode(pointerReferenceExpression); } @@ -993,184 +904,9 @@ public void VisitPointerReferenceExpression(PointerReferenceExpression pointerRe public void VisitPrimitiveExpression(PrimitiveExpression primitiveExpression) { StartNode(primitiveExpression); - if (!string.IsNullOrEmpty(primitiveExpression.LiteralValue)) { - formatter.WriteToken(primitiveExpression.LiteralValue); - } else { - WritePrimitiveValue(primitiveExpression.Value); - } + writer.WritePrimitiveValue(primitiveExpression.Value, primitiveExpression.UnsafeLiteralValue); EndNode(primitiveExpression); } - - public static string PrintPrimitiveValue(object val) - { - StringWriter writer = new StringWriter(); - CSharpOutputVisitor visitor = new CSharpOutputVisitor(writer, new CSharpFormattingOptions()); - visitor.WritePrimitiveValue(val); - return writer.ToString(); - } - - void WritePrimitiveValue(object val) - { - if (val == null) { - // usually NullReferenceExpression should be used for this, but we'll handle it anyways - WriteKeyword("null"); - return; - } - - if (val is bool) { - if ((bool)val) { - WriteKeyword("true"); - } else { - WriteKeyword("false"); - } - return; - } - - if (val is string) { - formatter.WriteToken("\"" + ConvertString(val.ToString()) + "\""); - lastWritten = LastWritten.Other; - } else if (val is char) { - formatter.WriteToken("'" + ConvertCharLiteral((char)val) + "'"); - lastWritten = LastWritten.Other; - } else if (val is decimal) { - formatter.WriteToken(((decimal)val).ToString(NumberFormatInfo.InvariantInfo) + "m"); - lastWritten = LastWritten.Other; - } else if (val is float) { - float f = (float)val; - if (float.IsInfinity(f) || float.IsNaN(f)) { - // Strictly speaking, these aren't PrimitiveExpressions; - // but we still support writing these to make life easier for code generators. - WriteKeyword("float"); - WriteToken(Roles.Dot); - if (float.IsPositiveInfinity(f)) { - WriteIdentifier("PositiveInfinity"); - } else if (float.IsNegativeInfinity(f)) { - WriteIdentifier("NegativeInfinity"); - } else { - WriteIdentifier("NaN"); - } - return; - } - if (f == 0 && 1 / f == float.NegativeInfinity) { - // negative zero is a special case - // (again, not a primitive expression, but it's better to handle - // the special case here than to do it in all code generators) - formatter.WriteToken("-"); - } - formatter.WriteToken(f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"); - lastWritten = LastWritten.Other; - } else if (val is double) { - double f = (double)val; - if (double.IsInfinity(f) || double.IsNaN(f)) { - // Strictly speaking, these aren't PrimitiveExpressions; - // but we still support writing these to make life easier for code generators. - WriteKeyword("double"); - WriteToken(Roles.Dot); - if (double.IsPositiveInfinity(f)) { - WriteIdentifier("PositiveInfinity"); - } else if (double.IsNegativeInfinity(f)) { - WriteIdentifier("NegativeInfinity"); - } else { - WriteIdentifier("NaN"); - } - return; - } - if (f == 0 && 1 / f == double.NegativeInfinity) { - // negative zero is a special case - // (again, not a primitive expression, but it's better to handle - // the special case here than to do it in all code generators) - formatter.WriteToken("-"); - } - string number = f.ToString("R", NumberFormatInfo.InvariantInfo); - if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { - number += ".0"; - } - formatter.WriteToken(number); - // needs space if identifier follows number; this avoids mistaking the following identifier as type suffix - lastWritten = LastWritten.KeywordOrIdentifier; - } else if (val is IFormattable) { - StringBuilder b = new StringBuilder (); - // if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) { - // b.Append("0x"); - // b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo)); - // } else { - b.Append(((IFormattable)val).ToString(null, NumberFormatInfo.InvariantInfo)); - // } - if (val is uint || val is ulong) { - b.Append("u"); - } - if (val is long || val is ulong) { - b.Append("L"); - } - formatter.WriteToken(b.ToString()); - // needs space if identifier follows number; this avoids mistaking the following identifier as type suffix - lastWritten = LastWritten.KeywordOrIdentifier; - } else { - formatter.WriteToken(val.ToString()); - lastWritten = LastWritten.Other; - } - } - - static string ConvertCharLiteral(char ch) - { - if (ch == '\'') { - return "\\'"; - } - return ConvertChar(ch); - } - - /// - /// Gets the escape sequence for the specified character. - /// - /// This method does not convert ' or ". - public static string ConvertChar(char ch) - { - switch (ch) { - case '\\': - return "\\\\"; - case '\0': - return "\\0"; - case '\a': - return "\\a"; - case '\b': - return "\\b"; - case '\f': - return "\\f"; - case '\n': - return "\\n"; - case '\r': - return "\\r"; - case '\t': - return "\\t"; - case '\v': - return "\\v"; - default: - if (char.IsControl(ch) || char.IsSurrogate(ch) || - // print all uncommon white spaces as numbers - (char.IsWhiteSpace(ch) && ch != ' ')) { - return "\\u" + ((int)ch).ToString("x4"); - } else { - return ch.ToString(); - } - } - } - - /// - /// Converts special characters to escape sequences within the given string. - /// - public static string ConvertString(string str) - { - StringBuilder sb = new StringBuilder (); - foreach (char ch in str) { - if (ch == '"') { - sb.Append("\\\""); - } else { - sb.Append(ConvertChar(ch)); - } - } - return sb.ToString(); - } - #endregion public void VisitSizeOfExpression(SizeOfExpression sizeOfExpression) @@ -1261,7 +997,7 @@ public void VisitQueryExpression(QueryExpression queryExpression) StartNode(queryExpression); bool indent = queryExpression.Parent is QueryClause && !(queryExpression.Parent is QueryContinuationClause); if (indent) { - formatter.Indent(); + writer.Indent(); NewLine(); } bool first = true; @@ -1276,7 +1012,7 @@ public void VisitQueryExpression(QueryExpression queryExpression) clause.AcceptVisitor(this); } if (indent) { - formatter.Unindent(); + writer.Unindent(); } EndNode(queryExpression); } @@ -1288,7 +1024,7 @@ public void VisitQueryContinuationClause(QueryContinuationClause queryContinuati Space(); WriteKeyword(QueryContinuationClause.IntoKeywordRole); Space(); - queryContinuationClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryContinuationClause.IdentifierToken); EndNode(queryContinuationClause); } @@ -1298,7 +1034,7 @@ public void VisitQueryFromClause(QueryFromClause queryFromClause) WriteKeyword(QueryFromClause.FromKeywordRole); queryFromClause.Type.AcceptVisitor(this); Space(); - queryFromClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryFromClause.IdentifierToken); Space(); WriteKeyword(QueryFromClause.InKeywordRole); Space(); @@ -1311,7 +1047,7 @@ public void VisitQueryLetClause(QueryLetClause queryLetClause) StartNode(queryLetClause); WriteKeyword(QueryLetClause.LetKeywordRole); Space(); - queryLetClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryLetClause.IdentifierToken); Space(policy.SpaceAroundAssignment); WriteToken(Roles.Assign); Space(policy.SpaceAroundAssignment); @@ -1334,7 +1070,7 @@ public void VisitQueryJoinClause(QueryJoinClause queryJoinClause) WriteKeyword(QueryJoinClause.JoinKeywordRole); queryJoinClause.Type.AcceptVisitor(this); Space(); - WriteIdentifier(queryJoinClause.JoinIdentifier, QueryJoinClause.JoinIdentifierRole); + WriteIdentifier(queryJoinClause.JoinIdentifierToken); Space(); WriteKeyword(QueryJoinClause.InKeywordRole); Space(); @@ -1350,7 +1086,7 @@ public void VisitQueryJoinClause(QueryJoinClause queryJoinClause) if (queryJoinClause.IsGroupJoin) { Space(); WriteKeyword(QueryJoinClause.IntoKeywordRole); - WriteIdentifier(queryJoinClause.IntoIdentifier, QueryJoinClause.IntoIdentifierRole); + WriteIdentifier(queryJoinClause.IntoIdentifierToken); } EndNode(queryJoinClause); } @@ -1422,7 +1158,7 @@ public void VisitAttributeSection(AttributeSection attributeSection) StartNode(attributeSection); WriteToken(Roles.LBracket); if (!string.IsNullOrEmpty(attributeSection.AttributeTarget)) { - WriteToken(attributeSection.AttributeTarget, Roles.AttributeTargetRole); + WriteIdentifier(attributeSection.AttributeTargetToken); WriteToken(Roles.Colon); Space(); } @@ -1444,7 +1180,7 @@ public void VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration) WriteKeyword(Roles.DelegateKeyword); delegateDeclaration.ReturnType.AcceptVisitor(this); Space(); - delegateDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(delegateDeclaration.NameToken); WriteTypeParameters(delegateDeclaration.TypeParameters); Space(policy.SpaceBeforeDelegateDeclarationParentheses); WriteCommaSeparatedListInParenthesis(delegateDeclaration.Parameters, policy.SpaceWithinMethodDeclarationParentheses); @@ -1466,7 +1202,7 @@ public void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration) MaybeNewLinesAfterUsings(member); } CloseBrace(policy.NamespaceBraceStyle); - OptionalSemicolon(); + OptionalSemicolon(namespaceDeclaration.LastChild); NewLine(); EndNode(namespaceDeclaration); } @@ -1495,7 +1231,7 @@ public void VisitTypeDeclaration(TypeDeclaration typeDeclaration) braceStyle = policy.ClassBraceStyle; break; } - typeDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(typeDeclaration.NameToken); WriteTypeParameters(typeDeclaration.TypeParameters); if (typeDeclaration.BaseTypes.Any()) { Space(); @@ -1509,6 +1245,7 @@ public void VisitTypeDeclaration(TypeDeclaration typeDeclaration) OpenBrace(braceStyle); if (typeDeclaration.ClassType == ClassType.Enum) { bool first = true; + AstNode last = null; foreach (var member in typeDeclaration.Members) { if (first) { first = false; @@ -1516,9 +1253,11 @@ public void VisitTypeDeclaration(TypeDeclaration typeDeclaration) Comma(member, noSpaceAfterComma: true); NewLine(); } + last = member; member.AcceptVisitor(this); } - OptionalComma(); + if (last != null) + OptionalComma(last.NextSibling); NewLine(); } else { bool first = true; @@ -1532,7 +1271,7 @@ public void VisitTypeDeclaration(TypeDeclaration typeDeclaration) } } CloseBrace(braceStyle); - OptionalSemicolon(); + OptionalSemicolon(typeDeclaration.LastChild); NewLine(); EndNode(typeDeclaration); } @@ -1541,7 +1280,7 @@ public void VisitUsingAliasDeclaration(UsingAliasDeclaration usingAliasDeclarati { StartNode(usingAliasDeclaration); WriteKeyword(UsingAliasDeclaration.UsingKeywordRole); - WriteIdentifier(usingAliasDeclaration.Alias, UsingAliasDeclaration.AliasRole); + WriteIdentifier(usingAliasDeclaration.GetChildByRole(UsingAliasDeclaration.AliasRole)); Space(policy.SpaceAroundEqualityOperator); WriteToken(Roles.Assign); Space(policy.SpaceAroundEqualityOperator); @@ -1566,7 +1305,7 @@ public void VisitExternAliasDeclaration(ExternAliasDeclaration externAliasDeclar Space(); WriteKeyword(Roles.AliasKeyword); Space(); - externAliasDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(externAliasDeclaration.NameToken); Semicolon(); EndNode(externAliasDeclaration); } @@ -1605,19 +1344,16 @@ public void VisitBlockStatement(BlockStatement blockStatement) foreach (var node in blockStatement.Statements) { node.AcceptVisitor(this); } - AstNode pos = positionStack.Pop(); - WriteSpecials(pos, null); - containerStack.Pop(); + EndNode(blockStatement); CloseBrace(style); if (!(blockStatement.Parent is Expression)) NewLine(); - formatter.EndNode(blockStatement); } public void VisitBreakStatement(BreakStatement breakStatement) { StartNode(breakStatement); - WriteKeyword("break"); + WriteKeyword("break", BreakStatement.BreakKeywordRole); Semicolon(); EndNode(breakStatement); } @@ -1633,7 +1369,7 @@ public void VisitCheckedStatement(CheckedStatement checkedStatement) public void VisitContinueStatement(ContinueStatement continueStatement) { StartNode(continueStatement); - WriteKeyword("continue"); + WriteKeyword("continue", ContinueStatement.ContinueKeywordRole); Semicolon(); EndNode(continueStatement); } @@ -1694,7 +1430,7 @@ public void VisitForeachStatement(ForeachStatement foreachStatement) Space(policy.SpacesWithinForeachParentheses); foreachStatement.VariableType.AcceptVisitor(this); Space(); - foreachStatement.VariableNameToken.AcceptVisitor(this); + WriteIdentifier(foreachStatement.VariableNameToken); WriteKeyword(ForeachStatement.InKeywordRole); Space(); foreachStatement.InExpression.AcceptVisitor(this); @@ -1755,7 +1491,7 @@ public void VisitGotoStatement(GotoStatement gotoStatement) { StartNode(gotoStatement); WriteKeyword(GotoStatement.GotoKeywordRole); - WriteIdentifier(gotoStatement.Label); + WriteIdentifier(gotoStatement.GetChildByRole(Roles.Identifier)); Semicolon(); EndNode(gotoStatement); } @@ -1781,7 +1517,7 @@ public void VisitIfElseStatement(IfElseStatement ifElseStatement) public void VisitLabelStatement(LabelStatement labelStatement) { StartNode(labelStatement); - WriteIdentifier(labelStatement.Label); + WriteIdentifier(labelStatement.GetChildByRole(Roles.Identifier)); WriteToken(Roles.Colon); bool foundLabelledStatement = false; for (AstNode tmp = labelStatement.NextSibling; tmp != null; tmp = tmp.NextSibling) { @@ -1835,7 +1571,7 @@ public void VisitSwitchStatement(SwitchStatement switchStatement) RPar(); OpenBrace(policy.StatementBraceStyle); if (!policy.IndentSwitchBody) { - formatter.Unindent(); + writer.Unindent(); } foreach (var section in switchStatement.SwitchSections) { @@ -1843,7 +1579,7 @@ public void VisitSwitchStatement(SwitchStatement switchStatement) } if (!policy.IndentSwitchBody) { - formatter.Indent(); + writer.Indent(); } CloseBrace(policy.StatementBraceStyle); NewLine(); @@ -1863,7 +1599,7 @@ public void VisitSwitchSection(SwitchSection switchSection) } bool isBlock = switchSection.Statements.Count == 1 && switchSection.Statements.Single() is BlockStatement; if (policy.IndentCaseBody && !isBlock) { - formatter.Indent(); + writer.Indent(); } if (!isBlock) @@ -1874,7 +1610,7 @@ public void VisitSwitchSection(SwitchSection switchSection) } if (policy.IndentCaseBody && !isBlock) { - formatter.Unindent(); + writer.Unindent(); } EndNode(switchSection); @@ -1932,7 +1668,7 @@ public void VisitCatchClause(CatchClause catchClause) catchClause.Type.AcceptVisitor(this); if (!string.IsNullOrEmpty(catchClause.VariableName)) { Space(); - catchClause.VariableNameToken.AcceptVisitor(this); + WriteIdentifier(catchClause.VariableNameToken); } Space(policy.SpacesWithinCatchParentheses); RPar(); @@ -2029,13 +1765,13 @@ public void VisitAccessor(Accessor accessor) WriteAttributes(accessor.Attributes); WriteModifiers(accessor.ModifierTokens); if (accessor.Role == PropertyDeclaration.GetterRole) { - WriteKeyword("get"); + WriteKeyword("get", PropertyDeclaration.GetKeywordRole); } else if (accessor.Role == PropertyDeclaration.SetterRole) { - WriteKeyword("set"); + WriteKeyword("set", PropertyDeclaration.SetKeywordRole); } else if (accessor.Role == CustomEventDeclaration.AddAccessorRole) { - WriteKeyword("add"); + WriteKeyword("add", CustomEventDeclaration.AddKeywordRole); } else if (accessor.Role == CustomEventDeclaration.RemoveAccessorRole) { - WriteKeyword("remove"); + WriteKeyword("remove", CustomEventDeclaration.RemoveKeywordRole); } WriteMethodBody(accessor.Body); EndNode(accessor); @@ -2047,12 +1783,10 @@ public void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclar WriteAttributes(constructorDeclaration.Attributes); WriteModifiers(constructorDeclaration.ModifierTokens); TypeDeclaration type = constructorDeclaration.Parent as TypeDeclaration; - var nameToken = constructorDeclaration.NameToken; - if (!nameToken.IsNull) - StartNode(nameToken); - WriteIdentifier(type != null ? type.Name : constructorDeclaration.Name); - if (!nameToken.IsNull) - EndNode(nameToken); + if (type != null && type.Name != constructorDeclaration.Name) + WriteIdentifier((Identifier)type.NameToken.Clone()); + else + WriteIdentifier(constructorDeclaration.NameToken); Space(policy.SpaceBeforeConstructorDeclarationParentheses); WriteCommaSeparatedListInParenthesis(constructorDeclaration.Parameters, policy.SpaceWithinMethodDeclarationParentheses); if (!constructorDeclaration.Initializer.IsNull) { @@ -2085,12 +1819,10 @@ public void VisitDestructorDeclaration(DestructorDeclaration destructorDeclarati WriteModifiers(destructorDeclaration.ModifierTokens); WriteToken(DestructorDeclaration.TildeRole); TypeDeclaration type = destructorDeclaration.Parent as TypeDeclaration; - var nameToken = destructorDeclaration.NameToken; - if (!nameToken.IsNull) - StartNode(nameToken); - WriteIdentifier(type != null ? type.Name : destructorDeclaration.Name); - if (!nameToken.IsNull) - EndNode(nameToken); + if (type != null && type.Name != destructorDeclaration.Name) + WriteIdentifier((Identifier)type.NameToken.Clone()); + else + WriteIdentifier(destructorDeclaration.NameToken); Space(policy.SpaceBeforeConstructorDeclarationParentheses); LPar(); RPar(); @@ -2103,7 +1835,7 @@ public void VisitEnumMemberDeclaration(EnumMemberDeclaration enumMemberDeclarati StartNode(enumMemberDeclaration); WriteAttributes(enumMemberDeclaration.Attributes); WriteModifiers(enumMemberDeclaration.ModifierTokens); - enumMemberDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(enumMemberDeclaration.NameToken); if (!enumMemberDeclaration.Initializer.IsNull) { Space(policy.SpaceAroundAssignment); WriteToken(Roles.Assign); @@ -2135,7 +1867,7 @@ public void VisitCustomEventDeclaration(CustomEventDeclaration customEventDeclar customEventDeclaration.ReturnType.AcceptVisitor(this); Space(); WritePrivateImplementationType(customEventDeclaration.PrivateImplementationType); - customEventDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(customEventDeclaration.NameToken); OpenBrace(policy.EventBraceStyle); // output add/remove in their original order foreach (AstNode node in customEventDeclaration.Children) { @@ -2177,7 +1909,7 @@ public void VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclarati public void VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer) { StartNode(fixedVariableInitializer); - fixedVariableInitializer.NameToken.AcceptVisitor(this); + WriteIdentifier(fixedVariableInitializer.NameToken); if (!fixedVariableInitializer.CountExpression.IsNull) { WriteToken(Roles.LBracket); Space(policy.SpacesWithinBrackets); @@ -2219,7 +1951,7 @@ public void VisitMethodDeclaration(MethodDeclaration methodDeclaration) methodDeclaration.ReturnType.AcceptVisitor(this); Space(); WritePrivateImplementationType(methodDeclaration.PrivateImplementationType); - methodDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(methodDeclaration.NameToken); WriteTypeParameters(methodDeclaration.TypeParameters); Space(policy.SpaceBeforeMethodDeclarationParentheses); WriteCommaSeparatedListInParenthesis(methodDeclaration.Parameters, policy.SpaceWithinMethodDeclarationParentheses); @@ -2245,7 +1977,7 @@ public void VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration) WriteKeyword(OperatorDeclaration.OperatorKeywordRole); Space(); if (operatorDeclaration.OperatorType == OperatorType.Explicit - || operatorDeclaration.OperatorType == OperatorType.Implicit) { + || operatorDeclaration.OperatorType == OperatorType.Implicit) { operatorDeclaration.ReturnType.AcceptVisitor(this); } else { WriteToken(OperatorDeclaration.GetToken(operatorDeclaration.OperatorType), OperatorDeclaration.GetRole(operatorDeclaration.OperatorType)); @@ -2279,7 +2011,7 @@ public void VisitParameterDeclaration(ParameterDeclaration parameterDeclaration) Space(); } if (!string.IsNullOrEmpty(parameterDeclaration.Name)) { - parameterDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(parameterDeclaration.NameToken); } if (!parameterDeclaration.DefaultExpression.IsNull) { Space(policy.SpaceAroundAssignment); @@ -2298,7 +2030,7 @@ public void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration) propertyDeclaration.ReturnType.AcceptVisitor(this); Space(); WritePrivateImplementationType(propertyDeclaration.PrivateImplementationType); - propertyDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(propertyDeclaration.NameToken); OpenBrace(policy.PropertyBraceStyle); // output get/set in their original order foreach (AstNode node in propertyDeclaration.Children) { @@ -2317,7 +2049,7 @@ public void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration) public void VisitVariableInitializer(VariableInitializer variableInitializer) { StartNode(variableInitializer); - variableInitializer.NameToken.AcceptVisitor(this); + WriteIdentifier(variableInitializer.NameToken); if (!variableInitializer.Initializer.IsNull) { Space(policy.SpaceAroundAssignment); WriteToken(Roles.Assign); @@ -2351,7 +2083,7 @@ public void VisitSyntaxTree(SyntaxTree syntaxTree) public void VisitSimpleType(SimpleType simpleType) { StartNode(simpleType); - WriteIdentifier(simpleType.Identifier); + WriteIdentifier(simpleType.IdentifierToken); WriteTypeArguments(simpleType.TypeArguments); EndNode(simpleType); } @@ -2365,7 +2097,7 @@ public void VisitMemberType(MemberType memberType) } else { WriteToken(Roles.Dot); } - WriteIdentifier(memberType.MemberName); + WriteIdentifier(memberType.MemberNameToken); WriteTypeArguments(memberType.TypeArguments); EndNode(memberType); } @@ -2391,9 +2123,7 @@ public void VisitArraySpecifier(ArraySpecifier arraySpecifier) StartNode(arraySpecifier); WriteToken(Roles.LBracket); foreach (var comma in arraySpecifier.GetChildrenByRole(Roles.Comma)) { - WriteSpecialsUpToNode(comma); - formatter.WriteToken(","); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Comma, ","); } WriteToken(Roles.RBracket); EndNode(arraySpecifier); @@ -2402,26 +2132,15 @@ public void VisitArraySpecifier(ArraySpecifier arraySpecifier) public void VisitPrimitiveType(PrimitiveType primitiveType) { StartNode(primitiveType); - WriteKeyword(primitiveType.Keyword); - if (primitiveType.Keyword == "new") { - // new() constraint - LPar(); - RPar(); - } + writer.WritePrimitiveType(primitiveType.Keyword); EndNode(primitiveType); } public void VisitComment(Comment comment) { - if (lastWritten == LastWritten.Division) { - // When there's a comment starting after a division operator - // "1.0 / /*comment*/a", then we need to insert a space in front of the comment. - formatter.Space(); - } - formatter.StartNode(comment); - formatter.WriteComment(comment.CommentType, comment.Content); - formatter.EndNode(comment); - lastWritten = LastWritten.Whitespace; + writer.StartNode(comment); + writer.WriteComment(comment.CommentType, comment.Content); + writer.EndNode(comment); } public void VisitNewLine(NewLineNode newLineNode) @@ -2443,10 +2162,9 @@ public void VisitText(TextNode textNode) public void VisitPreProcessorDirective(PreProcessorDirective preProcessorDirective) { - formatter.StartNode(preProcessorDirective); - formatter.WritePreProcessorDirective(preProcessorDirective.Type, preProcessorDirective.Argument); - formatter.EndNode(preProcessorDirective); - lastWritten = LastWritten.Whitespace; + writer.StartNode(preProcessorDirective); + writer.WritePreProcessorDirective(preProcessorDirective.Type, preProcessorDirective.Argument); + writer.EndNode(preProcessorDirective); } public void VisitTypeParameterDeclaration(TypeParameterDeclaration typeParameterDeclaration) @@ -2465,7 +2183,7 @@ public void VisitTypeParameterDeclaration(TypeParameterDeclaration typeParameter default: throw new NotSupportedException ("Invalid value for VarianceModifier"); } - typeParameterDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(typeParameterDeclaration.NameToken); EndNode(typeParameterDeclaration); } @@ -2474,7 +2192,7 @@ public void VisitConstraint(Constraint constraint) StartNode(constraint); Space(); WriteKeyword(Roles.WhereKeyword); - WriteIdentifier(constraint.TypeParameter.Identifier); + constraint.TypeParameter.AcceptVisitor(this); Space(); WriteToken(Roles.Colon); Space(); @@ -2486,9 +2204,9 @@ public void VisitCSharpTokenNode(CSharpTokenNode cSharpTokenNode) { CSharpModifierToken mod = cSharpTokenNode as CSharpModifierToken; if (mod != null) { - StartNode(mod); - WriteKeyword(CSharpModifierToken.GetModifierName(mod.Modifier)); - EndNode(mod); + // ITokenWriter assumes that each node processed between a + // StartNode(parentNode)-EndNode(parentNode)-pair is a child of parentNode. + WriteKeyword(CSharpModifierToken.GetModifierName(mod.Modifier), cSharpTokenNode.Role); } else { throw new NotSupportedException ("Should never visit individual tokens"); } @@ -2496,9 +2214,10 @@ public void VisitCSharpTokenNode(CSharpTokenNode cSharpTokenNode) public void VisitIdentifier(Identifier identifier) { - StartNode(identifier); - WriteIdentifier(identifier.Name); - EndNode(identifier); + // Do not call StartNode and EndNode for Identifier, because they are handled by the ITokenWriter. + // ITokenWriter assumes that each node processed between a + // StartNode(parentNode)-EndNode(parentNode)-pair is a child of parentNode. + WriteIdentifier(identifier); } #endregion @@ -2541,7 +2260,7 @@ void VisitChoice(Choice choice) Space(); LPar(); NewLine(); - formatter.Indent(); + writer.Indent(); foreach (INode alternative in choice) { VisitNodeInPattern(alternative); if (alternative != choice.Last()) { @@ -2549,7 +2268,7 @@ void VisitChoice(Choice choice) } NewLine(); } - formatter.Unindent(); + writer.Unindent(); RPar(); } @@ -2603,7 +2322,7 @@ void VisitNodeInPattern(INode childNode) } else if (childNode is Repeat) { VisitRepeat((Repeat)childNode); } else { - WritePrimitiveValue(childNode); + TextWriterTokenWriter.PrintPrimitiveValue(childNode); } } #endregion @@ -2641,7 +2360,7 @@ public void VisitDocumentationReference(DocumentationReference documentationRefe } break; default: - WriteIdentifier(documentationReference.MemberName); + WriteIdentifier(documentationReference.GetChildByRole(Roles.Identifier)); break; } WriteTypeArguments(documentationReference.TypeArguments); @@ -2656,5 +2375,13 @@ public void VisitDocumentationReference(DocumentationReference documentationRefe EndNode(documentationReference); } #endregion + + /// + /// Converts special characters to escape sequences within the given string. + /// + public static string ConvertString(string text) + { + return TextWriterTokenWriter.ConvertString(text); + } } } diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs deleted file mode 100644 index f780a8ef0..000000000 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2010-2013 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; - -namespace ICSharpCode.NRefactory.CSharp -{ - /// - /// Output formatter for the Output visitor. - /// - public interface IOutputFormatter - { - void StartNode(AstNode node); - void EndNode(AstNode node); - - /// - /// Writes an identifier. - /// If the identifier conflicts with a keyword, the output visitor will - /// call WriteToken("@") before calling WriteIdentifier(). - /// - void WriteIdentifier(string identifier); - - /// - /// Writes a keyword to the output. - /// - void WriteKeyword(string keyword); - - /// - /// Writes a token to the output. - /// - void WriteToken(string token); - void Space(); - - void OpenBrace(BraceStyle style); - void CloseBrace(BraceStyle style); - - void Indent(); - void Unindent(); - - void NewLine(); - - void WriteComment(CommentType commentType, string content); - void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument); - } -} diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs new file mode 100644 index 000000000..31b73f987 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs @@ -0,0 +1,161 @@ +// Copyright (c) 2010-2013 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.IO; + +namespace ICSharpCode.NRefactory.CSharp +{ + public abstract class TokenWriter + { + public abstract void StartNode(AstNode node); + public abstract void EndNode(AstNode node); + + /// + /// Writes an identifier. + /// + public abstract void WriteIdentifier(Identifier identifier); + + /// + /// Writes a keyword to the output. + /// + public abstract void WriteKeyword(Role role, string keyword); + + /// + /// Writes a token to the output. + /// + public abstract void WriteToken(Role role, string token); + + /// + /// Writes a primitive/literal value + /// + public abstract void WritePrimitiveValue(object value, string literalValue = null); + + public abstract void WritePrimitiveType(string type); + + public abstract void Space(); + public abstract void Indent(); + public abstract void Unindent(); + public abstract void NewLine(); + + public abstract void WriteComment(CommentType commentType, string content); + public abstract void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument); + + public static TokenWriter Create(TextWriter writer, string indentation = "\t") + { + return new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new TextWriterTokenWriter(writer) { IndentationString = indentation })); + } + + public static TokenWriter CreateWriterThatSetsLocationsInAST(TextWriter writer, string indentation = "\t") + { + var target = new TextWriterTokenWriter(writer) { IndentationString = indentation }; + return new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new InsertMissingTokensDecorator(target, target))); + } + + public static TokenWriter WrapInWriterThatSetsLocationsInAST(TokenWriter writer) + { + if (!(writer is ILocatable)) + throw new InvalidOperationException("writer does not provide locations!"); + return new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new InsertMissingTokensDecorator(writer, (ILocatable)writer))); + } + } + + public interface ILocatable + { + TextLocation Location { get; } + } + + public abstract class DecoratingTokenWriter : TokenWriter + { + TokenWriter decoratedWriter; + + protected DecoratingTokenWriter(TokenWriter decoratedWriter) + { + if (decoratedWriter == null) + throw new ArgumentNullException("decoratedWriter"); + this.decoratedWriter = decoratedWriter; + } + + public override void StartNode(AstNode node) + { + decoratedWriter.StartNode(node); + } + + public override void EndNode(AstNode node) + { + decoratedWriter.EndNode(node); + } + + public override void WriteIdentifier(Identifier identifier) + { + decoratedWriter.WriteIdentifier(identifier); + } + + public override void WriteKeyword(Role role, string keyword) + { + decoratedWriter.WriteKeyword(role, keyword); + } + + public override void WriteToken(Role role, string token) + { + decoratedWriter.WriteToken(role, token); + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + decoratedWriter.WritePrimitiveValue(value, literalValue); + } + + public override void WritePrimitiveType(string type) + { + decoratedWriter.WritePrimitiveType(type); + } + + public override void Space() + { + decoratedWriter.Space(); + } + + public override void Indent() + { + decoratedWriter.Indent(); + } + + public override void Unindent() + { + decoratedWriter.Unindent(); + } + + public override void NewLine() + { + decoratedWriter.NewLine(); + } + + public override void WriteComment(CommentType commentType, string content) + { + decoratedWriter.WriteComment(commentType, content); + } + + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + { + decoratedWriter.WritePreProcessorDirective(type, argument); + } + } +} + + diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs new file mode 100644 index 000000000..4f390d92d --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs @@ -0,0 +1,122 @@ +// Copyright (c) 2010-2013 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.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.NRefactory.CSharp +{ + class InsertMissingTokensDecorator : DecoratingTokenWriter + { + readonly Stack> nodes = new Stack>(); + List currentList; + readonly ILocatable locationProvider; + + public InsertMissingTokensDecorator(TokenWriter writer, ILocatable locationProvider) + : base(writer) + { + this.locationProvider = locationProvider; + currentList = new List(); + } + + public override void StartNode(AstNode node) + { + currentList.Add(node); + nodes.Push(currentList); + currentList = new List(); + base.StartNode(node); + } + + public override void EndNode(AstNode node) + { + System.Diagnostics.Debug.Assert(currentList != null); + foreach (var removable in node.Children.Where(n => n is CSharpTokenNode)) { + removable.Remove(); + } + foreach (var child in currentList) { + System.Diagnostics.Debug.Assert(child.Parent == null || node == child.Parent); + child.Remove(); + node.AddChildWithExistingRole(child); + } + currentList = nodes.Pop(); + base.EndNode(node); + } + + public override void WriteToken(Role role, string token) + { + CSharpTokenNode t = new CSharpTokenNode(locationProvider.Location, (TokenRole)role); + EmptyStatement node = nodes.Peek().LastOrDefault() as EmptyStatement; + if (node == null) + currentList.Add(t); + else { + node.Location = locationProvider.Location; + } + base.WriteToken(role, token); + } + + public override void WriteKeyword(Role role, string keyword) + { + TextLocation start = locationProvider.Location; + CSharpTokenNode t = null; + if (role is TokenRole) + t = new CSharpTokenNode(start, (TokenRole)role); + else if (role == EntityDeclaration.ModifierRole) + t = new CSharpModifierToken(start, CSharpModifierToken.GetModifierValue(keyword)); + else if (keyword == "this") { + ThisReferenceExpression node = nodes.Peek().LastOrDefault() as ThisReferenceExpression; + if (node != null) + node.Location = start; + } else if (keyword == "base") { + BaseReferenceExpression node = nodes.Peek().LastOrDefault() as BaseReferenceExpression; + if (node != null) + node.Location = start; + } + if (t != null) currentList.Add(t); + base.WriteKeyword(role, keyword); + } + + public override void WriteIdentifier(Identifier identifier) + { + if (!identifier.IsNull) + identifier.SetStartLocation(locationProvider.Location); + currentList.Add(identifier); + base.WriteIdentifier(identifier); + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + Expression node = nodes.Peek().LastOrDefault() as Expression; + if (node is PrimitiveExpression) { + ((PrimitiveExpression)node).SetStartLocation(locationProvider.Location); + } + if (node is NullReferenceExpression) { + ((NullReferenceExpression)node).SetStartLocation(locationProvider.Location); + } + base.WritePrimitiveValue(value, literalValue); + } + + public override void WritePrimitiveType(string type) + { + PrimitiveType node = nodes.Peek().LastOrDefault() as PrimitiveType; + if (node != null) + node.SetStartLocation(locationProvider.Location); + base.WritePrimitiveType(type); + } + } +} diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs new file mode 100644 index 000000000..8cb616df3 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs @@ -0,0 +1,184 @@ +// Copyright (c) 2010-2013 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.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.NRefactory.PatternMatching; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.NRefactory.CSharp +{ + class InsertRequiredSpacesDecorator : DecoratingTokenWriter + { + /// + /// Used to insert the minimal amount of spaces so that the lexer recognizes the tokens that were written. + /// + LastWritten lastWritten; + + enum LastWritten + { + Whitespace, + Other, + KeywordOrIdentifier, + Plus, + Minus, + Ampersand, + QuestionMark, + Division + } + + public InsertRequiredSpacesDecorator(TokenWriter writer) + : base(writer) + { + } + + public override void WriteIdentifier(Identifier identifier) + { + if (identifier.IsVerbatim) { + if (lastWritten == LastWritten.KeywordOrIdentifier) { + // this space is not strictly required, so we call Space() + Space(); + } + } else if (lastWritten == LastWritten.KeywordOrIdentifier) { + // this space is strictly required, so we directly call the formatter + base.Space(); + } + base.WriteIdentifier(identifier); + lastWritten = LastWritten.KeywordOrIdentifier; + } + + public override void WriteKeyword(Role role, string keyword) + { + if (lastWritten == LastWritten.KeywordOrIdentifier) { + Space(); + } + base.WriteKeyword(role, keyword); + lastWritten = LastWritten.KeywordOrIdentifier; + } + + public override void WriteToken(Role role, string token) + { + // Avoid that two +, - or ? tokens are combined into a ++, -- or ?? token. + // Note that we don't need to handle tokens like = because there's no valid + // C# program that contains the single token twice in a row. + // (for +, - and &, this can happen with unary operators; + // for ?, this can happen in "a is int? ? b : c" or "a as int? ?? 0"; + // and for /, this can happen with "1/ *ptr" or "1/ //comment".) + if (lastWritten == LastWritten.Plus && token[0] == '+' || + lastWritten == LastWritten.Minus && token[0] == '-' || + lastWritten == LastWritten.Ampersand && token[0] == '&' || + lastWritten == LastWritten.QuestionMark && token[0] == '?' || + lastWritten == LastWritten.Division && token[0] == '*') { + base.Space(); + } + base.WriteToken(role, token); + if (token == "+") { + lastWritten = LastWritten.Plus; + } else if (token == "-") { + lastWritten = LastWritten.Minus; + } else if (token == "&") { + lastWritten = LastWritten.Ampersand; + } else if (token == "?") { + lastWritten = LastWritten.QuestionMark; + } else if (token == "/") { + lastWritten = LastWritten.Division; + } else { + lastWritten = LastWritten.Other; + } + } + + public override void Space() + { + base.Space(); + lastWritten = LastWritten.Whitespace; + } + + public override void NewLine() + { + base.NewLine(); + lastWritten = LastWritten.Whitespace; + } + + public override void WriteComment(CommentType commentType, string content) + { + if (lastWritten == LastWritten.Division) { + // When there's a comment starting after a division operator + // "1.0 / /*comment*/a", then we need to insert a space in front of the comment. + base.Space(); + } + base.WriteComment(commentType, content); + lastWritten = LastWritten.Whitespace; + } + + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + { + base.WritePreProcessorDirective(type, argument); + lastWritten = LastWritten.Whitespace; + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + base.WritePrimitiveValue(value, literalValue); + if (value == null || value is bool) + return; + if (value is string) { + lastWritten = LastWritten.Other; + } else if (value is char) { + lastWritten = LastWritten.Other; + } else if (value is decimal) { + lastWritten = LastWritten.Other; + } else if (value is float) { + float f = (float)value; + if (float.IsInfinity(f) || float.IsNaN(f)) return; + lastWritten = LastWritten.Other; + } else if (value is double) { + double f = (double)value; + if (double.IsInfinity(f) || double.IsNaN(f)) return; + // needs space if identifier follows number; + // this avoids mistaking the following identifier as type suffix + lastWritten = LastWritten.KeywordOrIdentifier; + } else if (value is IFormattable) { + // needs space if identifier follows number; + // this avoids mistaking the following identifier as type suffix + lastWritten = LastWritten.KeywordOrIdentifier; + } else { + lastWritten = LastWritten.Other; + } + } + + public override void WritePrimitiveType(string type) + { + if (lastWritten == LastWritten.KeywordOrIdentifier) { + Space(); + } + base.WritePrimitiveType(type); + if (type == "new") { + lastWritten = LastWritten.Other; + } else { + lastWritten = LastWritten.KeywordOrIdentifier; + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs new file mode 100644 index 000000000..0fafdeef0 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs @@ -0,0 +1,157 @@ +// Copyright (c) 2010-2013 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.NRefactory.CSharp +{ + class InsertSpecialsDecorator : DecoratingTokenWriter + { + readonly Stack positionStack = new Stack(); + int visitorWroteNewLine = 0; + + public InsertSpecialsDecorator(TokenWriter writer) : base(writer) + { + } + + public override void StartNode(AstNode node) + { + if (positionStack.Count > 0) { + WriteSpecialsUpToNode(node); + } + positionStack.Push(node.FirstChild); + base.StartNode(node); + } + + public override void EndNode(AstNode node) + { + base.EndNode(node); + AstNode pos = positionStack.Pop(); + Debug.Assert(pos == null || pos.Parent == node); + WriteSpecials(pos, null); + } + + public override void WriteKeyword(Role role, string keyword) + { + if (role != null) { + WriteSpecialsUpToRole(role); + } + base.WriteKeyword(role, keyword); + } + + public override void WriteIdentifier(Identifier identifier) + { + WriteSpecialsUpToRole(identifier.Role ?? Roles.Identifier); + base.WriteIdentifier(identifier); + } + + public override void WriteToken(Role role, string token) + { + WriteSpecialsUpToRole(role); + base.WriteToken(role, token); + } + + public override void NewLine() + { + if (visitorWroteNewLine >= 0) + base.NewLine(); + visitorWroteNewLine++; + } + + #region WriteSpecials + /// + /// Writes all specials from start to end (exclusive). Does not touch the positionStack. + /// + void WriteSpecials(AstNode start, AstNode end) + { + for (AstNode pos = start; pos != end; pos = pos.NextSibling) { + if (pos.Role == Roles.Comment) { + var node = (Comment)pos; + base.WriteComment(node.CommentType, node.Content); + } + // see CSharpOutputVisitor.VisitNewLine() + // if (pos.Role == Roles.NewLine) { + // if (visitorWroteNewLine <= 0) + // base.NewLine(); + // visitorWroteNewLine--; + // } + if (pos.Role == Roles.PreProcessorDirective) { + var node = (PreProcessorDirective)pos; + base.WritePreProcessorDirective(node.Type, node.Argument); + } + } + } + + /// + /// Writes all specials between the current position (in the positionStack) and the next + /// node with the specified role. Advances the current position. + /// + void WriteSpecialsUpToRole(Role role) + { + WriteSpecialsUpToRole(role, null); + } + + 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 == role) { + WriteSpecials(positionStack.Pop(), 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; + } + } + } + + /// + /// Writes all specials between the current position (in the positionStack) and the specified node. + /// Advances the current position. + /// + void WriteSpecialsUpToNode(AstNode node) + { + if (positionStack.Count == 0) { + return; + } + for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) { + if (pos == node) { + WriteSpecials(positionStack.Pop(), 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; + } + } + } + #endregion + } +} + + + + diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs index 4d1a0d364..4b9a262c6 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs @@ -17,139 +17,79 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Globalization; using System.IO; +using System.Text; namespace ICSharpCode.NRefactory.CSharp { /// /// Writes C# code into a TextWriter. /// - public class TextWriterOutputFormatter : IOutputFormatter + public class TextWriterTokenWriter : TokenWriter, ILocatable { readonly TextWriter textWriter; int indentation; bool needsIndent = true; bool isAtStartOfLine = true; + int line, column; public int Indentation { - get { - return this.indentation; - } - set { - this.indentation = value; - } + get { return this.indentation; } + set { this.indentation = value; } + } + + public TextLocation Location { + get { return new TextLocation(line, column + (needsIndent ? indentation * IndentationString.Length : 0)); } } public string IndentationString { get; set; } - public TextWriterOutputFormatter(TextWriter textWriter) + public TextWriterTokenWriter(TextWriter textWriter) { if (textWriter == null) throw new ArgumentNullException("textWriter"); this.textWriter = textWriter; this.IndentationString = "\t"; + this.line = 1; + this.column = 1; } - public void WriteIdentifier(string ident) + public override void WriteIdentifier(Identifier identifier) { WriteIndentation(); - textWriter.Write(ident); + if (identifier.IsVerbatim) { + textWriter.Write('@'); + column++; + } + textWriter.Write(identifier.Name); + column += identifier.Name.Length; isAtStartOfLine = false; } - public void WriteKeyword(string keyword) + public override void WriteKeyword(Role role, string keyword) { WriteIndentation(); + column += keyword.Length; textWriter.Write(keyword); isAtStartOfLine = false; } - public void WriteToken(string token) + public override void WriteToken(Role role, string token) { WriteIndentation(); + column += token.Length; textWriter.Write(token); isAtStartOfLine = false; } - public void Space() + public override void Space() { WriteIndentation(); + column++; textWriter.Write(' '); } - public void OpenBrace(BraceStyle style) - { - switch (style) { - case BraceStyle.DoNotChange: - case BraceStyle.EndOfLine: - case BraceStyle.BannerStyle: - WriteIndentation(); - if (!isAtStartOfLine) - textWriter.Write(' '); - textWriter.Write('{'); - break; - case BraceStyle.EndOfLineWithoutSpace: - WriteIndentation(); - textWriter.Write('{'); - break; - case BraceStyle.NextLine: - if (!isAtStartOfLine) - NewLine(); - WriteIndentation(); - textWriter.Write('{'); - break; - - case BraceStyle.NextLineShifted: - NewLine (); - Indent(); - WriteIndentation(); - textWriter.Write('{'); - NewLine(); - return; - case BraceStyle.NextLineShifted2: - NewLine (); - Indent(); - WriteIndentation(); - textWriter.Write('{'); - break; - default: - throw new ArgumentOutOfRangeException (); - } - Indent(); - NewLine(); - } - - public void CloseBrace(BraceStyle style) - { - switch (style) { - case BraceStyle.DoNotChange: - case BraceStyle.EndOfLine: - case BraceStyle.EndOfLineWithoutSpace: - case BraceStyle.NextLine: - Unindent(); - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - break; - case BraceStyle.BannerStyle: - case BraceStyle.NextLineShifted: - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - Unindent(); - break; - case BraceStyle.NextLineShifted2: - Unindent(); - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - Unindent(); - break; - default: - throw new ArgumentOutOfRangeException (); - } - } - protected void WriteIndentation() { if (needsIndent) { @@ -157,33 +97,37 @@ protected void WriteIndentation() for (int i = 0; i < indentation; i++) { textWriter.Write(this.IndentationString); } + column += indentation * IndentationString.Length; } } - public void NewLine() + public override void NewLine() { textWriter.WriteLine(); + column = 1; + line++; needsIndent = true; isAtStartOfLine = true; } - public void Indent() + public override void Indent() { indentation++; } - public void Unindent() + public override void Unindent() { indentation--; } - public void WriteComment(CommentType commentType, string content) + public override void WriteComment(CommentType commentType, string content) { WriteIndentation(); switch (commentType) { case CommentType.SingleLine: textWriter.Write("//"); textWriter.WriteLine(content); + column += 2 + content.Length; needsIndent = true; isAtStartOfLine = true; break; @@ -191,36 +135,263 @@ public void WriteComment(CommentType commentType, string content) textWriter.Write("/*"); textWriter.Write(content); textWriter.Write("*/"); + column += 2; + UpdateEndLocation(content, ref line, ref column); + column += 2; isAtStartOfLine = false; break; case CommentType.Documentation: textWriter.Write("///"); textWriter.WriteLine(content); + column += 3 + content.Length; needsIndent = true; isAtStartOfLine = true; break; default: textWriter.Write(content); + column += content.Length; break; } } - public void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + static void UpdateEndLocation(string content, ref int line, ref int column) + { + if (string.IsNullOrEmpty(content)) + return; + for (int i = 0; i < content.Length; i++) { + char ch = content[i]; + switch (ch) { + case '\r': + if (i + 1 < content.Length && content[i + 1] == '\n') + i++; + goto case '\n'; + case '\n': + line++; + column = 0; + break; + } + column++; + } + } + + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) { // pre-processor directive must start on its own line if (!isAtStartOfLine) NewLine(); WriteIndentation(); textWriter.Write('#'); - textWriter.Write(type.ToString().ToLowerInvariant()); + string directive = type.ToString().ToLowerInvariant(); + textWriter.Write(directive); + column += 1 + directive.Length; if (!string.IsNullOrEmpty(argument)) { textWriter.Write(' '); textWriter.Write(argument); + column += 1 + argument.Length; } NewLine(); } - public virtual void StartNode(AstNode node) + public static string PrintPrimitiveValue(object value) + { + TextWriter writer = new StringWriter(); + TextWriterTokenWriter tokenWriter = new TextWriterTokenWriter(writer); + tokenWriter.WritePrimitiveValue(value); + return writer.ToString(); + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + if (literalValue != null) { + textWriter.Write(literalValue); + column += literalValue.Length; + return; + } + + if (value == null) { + // usually NullReferenceExpression should be used for this, but we'll handle it anyways + textWriter.Write("null"); + column += 4; + return; + } + + if (value is bool) { + if ((bool)value) { + textWriter.Write("true"); + column += 4; + } else { + textWriter.Write("false"); + column += 5; + } + return; + } + + if (value is string) { + string tmp = "\"" + ConvertString(value.ToString()) + "\""; + column += tmp.Length; + textWriter.Write(tmp); + } else if (value is char) { + string tmp = "'" + ConvertCharLiteral((char)value) + "'"; + column += tmp.Length; + textWriter.Write(tmp); + } else if (value is decimal) { + string str = ((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m"; + column += str.Length; + textWriter.Write(str); + } else if (value is float) { + float f = (float)value; + if (float.IsInfinity(f) || float.IsNaN(f)) { + // Strictly speaking, these aren't PrimitiveExpressions; + // but we still support writing these to make life easier for code generators. + textWriter.Write("float"); + column += 5; + WriteToken(Roles.Dot, "."); + if (float.IsPositiveInfinity(f)) { + textWriter.Write("PositiveInfinity"); + column += "PositiveInfinity".Length; + } else if (float.IsNegativeInfinity(f)) { + textWriter.Write("NegativeInfinity"); + column += "NegativeInfinity".Length; + } else { + textWriter.Write("NaN"); + column += 3; + } + return; + } + if (f == 0 && 1 / f == float.NegativeInfinity) { + // negative zero is a special case + // (again, not a primitive expression, but it's better to handle + // the special case here than to do it in all code generators) + textWriter.Write("-"); + column++; + } + var str = f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"; + column += str.Length; + textWriter.Write(str); + } else if (value is double) { + double f = (double)value; + if (double.IsInfinity(f) || double.IsNaN(f)) { + // Strictly speaking, these aren't PrimitiveExpressions; + // but we still support writing these to make life easier for code generators. + textWriter.Write("double"); + column += 6; + WriteToken(Roles.Dot, "."); + if (double.IsPositiveInfinity(f)) { + textWriter.Write("PositiveInfinity"); + column += "PositiveInfinity".Length; + } else if (double.IsNegativeInfinity(f)) { + textWriter.Write("NegativeInfinity"); + column += "NegativeInfinity".Length; + } else { + textWriter.Write("NaN"); + column += 3; + } + return; + } + if (f == 0 && 1 / f == double.NegativeInfinity) { + // negative zero is a special case + // (again, not a primitive expression, but it's better to handle + // the special case here than to do it in all code generators) + textWriter.Write("-"); + } + string number = f.ToString("R", NumberFormatInfo.InvariantInfo); + if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { + number += ".0"; + } + textWriter.Write(number); + } else if (value is IFormattable) { + StringBuilder b = new StringBuilder (); +// if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) { +// b.Append("0x"); +// b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo)); +// } else { + b.Append(((IFormattable)value).ToString(null, NumberFormatInfo.InvariantInfo)); +// } + if (value is uint || value is ulong) { + b.Append("u"); + } + if (value is long || value is ulong) { + b.Append("L"); + } + textWriter.Write(b.ToString()); + column += b.Length; + } else { + textWriter.Write(value.ToString()); + column += value.ToString().Length; + } + } + + static string ConvertCharLiteral(char ch) + { + if (ch == '\'') { + return "\\'"; + } + return ConvertChar(ch); + } + + /// + /// Gets the escape sequence for the specified character. + /// + /// This method does not convert ' or ". + public static string ConvertChar(char ch) + { + switch (ch) { + case '\\': + return "\\\\"; + case '\0': + return "\\0"; + case '\a': + return "\\a"; + case '\b': + return "\\b"; + case '\f': + return "\\f"; + case '\n': + return "\\n"; + case '\r': + return "\\r"; + case '\t': + return "\\t"; + case '\v': + return "\\v"; + default: + if (char.IsControl(ch) || char.IsSurrogate(ch) || + // print all uncommon white spaces as numbers + (char.IsWhiteSpace(ch) && ch != ' ')) { + return "\\u" + ((int)ch).ToString("x4"); + } else { + return ch.ToString(); + } + } + } + + /// + /// Converts special characters to escape sequences within the given string. + /// + public static string ConvertString(string str) + { + StringBuilder sb = new StringBuilder (); + foreach (char ch in str) { + if (ch == '"') { + sb.Append("\\\""); + } else { + sb.Append(ConvertChar(ch)); + } + } + return sb.ToString(); + } + + public override void WritePrimitiveType(string type) + { + textWriter.Write(type); + column += type.Length; + if (type == "new") { + textWriter.Write("()"); + column += 2; + } + } + + public override void StartNode(AstNode node) { // Write out the indentation, so that overrides of this method // can rely use the current output length to identify the position of the node @@ -228,7 +399,7 @@ public virtual void StartNode(AstNode node) WriteIndentation(); } - public virtual void EndNode(AstNode node) + public override void EndNode(AstNode node) { } } diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs index bf298d081..e58caa6dc 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs @@ -25,12 +25,15 @@ // THE SOFTWARE. using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.TypeSystem; using System.Threading.Tasks; using System.Linq; using System.Text; +using Mono.CSharp; +using ITypeDefinition = ICSharpCode.NRefactory.TypeSystem.ITypeDefinition; namespace ICSharpCode.NRefactory.CSharp.Refactoring { @@ -163,18 +166,55 @@ public void InsertBefore(AstNode node, AstNode newNode) public void InsertAfter(AstNode node, AstNode newNode) { - var indentOffset = GetCurrentOffset(new TextLocation(node.StartLocation.Line, 1)); - var output = OutputNode (GetIndentLevelAt (indentOffset), newNode); - string text = output.Text; - if (!(newNode is Expression || newNode is AstType)) - text = Options.EolMarker + text; - var insertOffset = GetCurrentOffset(node.EndLocation); - InsertText(insertOffset, text); - output.RegisterTrackedSegments(this, insertOffset); - CorrectFormatting (node, newNode); + var indentLevel = IndentLevelFor(node); + var output = OutputNode(indentLevel, newNode); + string text = PrefixFor(node, newNode) + output.Text; + + var insertOffset = GetCurrentOffset(node.EndLocation); + InsertText(insertOffset, text); + output.RegisterTrackedSegments(this, insertOffset); + CorrectFormatting (node, newNode); } - public void AddTo(BlockStatement bodyStatement, AstNode newNode) + private int IndentLevelFor(AstNode node) + { + if (!DoesInsertingAfterRequireNewline(node)) + return 0; + + return GetIndentLevelAt(GetCurrentOffset(new TextLocation(node.StartLocation.Line, 1))); + } + + bool DoesInsertingAfterRequireNewline(AstNode node) + { + if (node is Expression) + return false; + + if (node is AstType) + return false; + + if (node is ParameterDeclaration) + return false; + + var token = node as CSharpTokenNode; + if (token != null && token.Role == Roles.LPar) + return false; + + return true; + } + + private string PrefixFor(AstNode node, AstNode newNode) + { + if (DoesInsertingAfterRequireNewline(node)) + return Options.EolMarker; + + if (newNode is ParameterDeclaration && node is ParameterDeclaration) + //todo: worry about adding characters to the document without matching AstNode's. + return ", "; + + return String.Empty; + } + + public void AddTo(BlockStatement bodyStatement, AstNode newNode) { var startOffset = GetCurrentOffset(bodyStatement.LBraceToken.EndLocation); var output = OutputNode(1 + GetIndentLevelAt(startOffset), newNode, true); @@ -436,18 +476,26 @@ protected virtual int GetIndentLevelAt (int offset) return 0; } - sealed class SegmentTrackingOutputFormatter : TextWriterOutputFormatter + sealed class SegmentTrackingTokenWriter : TextWriterTokenWriter { internal List> NewSegments = new List>(); readonly Stack startOffsets = new Stack(); readonly StringWriter stringWriter; - public SegmentTrackingOutputFormatter (StringWriter stringWriter) + public SegmentTrackingTokenWriter(StringWriter stringWriter) : base(stringWriter) { this.stringWriter = stringWriter; } + public override void WriteIdentifier (Identifier identifier) + { + int startOffset = stringWriter.GetStringBuilder ().Length; + int endOffset = startOffset + (identifier.Name ?? "").Length + (identifier.IsVerbatim ? 1 : 0); + NewSegments.Add(new KeyValuePair(identifier, new Segment(startOffset, endOffset))); + base.WriteIdentifier (identifier); + } + public override void StartNode (AstNode node) { base.StartNode (node); @@ -466,7 +514,7 @@ public override void EndNode (AstNode node) protected NodeOutput OutputNode(int indentLevel, AstNode node, bool startWithNewLine = false) { var stringWriter = new StringWriter (); - var formatter = new SegmentTrackingOutputFormatter (stringWriter); + var formatter = new SegmentTrackingTokenWriter(stringWriter); formatter.Indentation = indentLevel; formatter.IndentationString = Options.TabsToSpaces ? new string (' ', Options.IndentSize) : "\t"; stringWriter.NewLine = Options.EolMarker; diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/TypeGuessing.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/TypeGuessing.cs index 6a08cc5d2..f43e3ece4 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/TypeGuessing.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/TypeGuessing.cs @@ -146,6 +146,14 @@ public static IEnumerable GetValidTypes(CSharpAstResolver resolver, AstNo return new [] { resolver.Compilation.FindType (KnownTypeCode.Boolean) }; } + var mref = expr as MemberReferenceExpression; + if (mref != null) { + // case: guess enum when trying to access not existent enum member + var rr = resolver.Resolve(mref.Target); + if (!rr.IsError && rr.Type.Kind == TypeKind.Enum) + return new [] { rr.Type }; + } + if (expr.Parent is ParenthesizedExpression || expr.Parent is NamedArgumentExpression) { return GetValidTypes(resolver, expr.Parent); } diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs index ea2d9cb1c..2fdd543e4 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs @@ -241,6 +241,8 @@ TypeParameterSubstitution IMember.Substitution { IMember IMember.Specialize(TypeParameterSubstitution substitution) { + if (TypeParameterSubstitution.Identity.Equals(substitution)) + return this; throw new NotSupportedException(); } diff --git a/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs b/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs index 88f61f1c9..cbff80cc0 100644 --- a/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs +++ b/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.NRefactory.CSharp.TypeSystem /// Represents a file that was parsed and converted for the type system. /// [Serializable, FastSerializerVersion(TypeSystemConvertVisitor.version)] - public sealed class CSharpUnresolvedFile : AbstractFreezable, IUnresolvedFile, IUnresolvedDocumentationProvider + public class CSharpUnresolvedFile : AbstractFreezable, IUnresolvedFile, IUnresolvedDocumentationProvider { // The 'FastSerializerVersion' attribute on CSharpUnresolvedFile must be incremented when fixing // bugs in the TypeSystemConvertVisitor diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Xml/XmlReaderTest.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/XmlReaderTest.cs index feba28b5c..ea3ed47de 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/Xml/XmlReaderTest.cs +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/XmlReaderTest.cs @@ -92,9 +92,9 @@ static string ToString(object val) if (val == null) return "null"; else if (val is string) - return "\"" + CSharpOutputVisitor.ConvertString((string)val) + "\""; + return "\"" + TextWriterTokenWriter.ConvertString((string)val) + "\""; else if (val is char) - return "'" + CSharpOutputVisitor.ConvertChar((char)val) + "'"; + return "'" + TextWriterTokenWriter.ConvertChar((char)val) + "'"; else return val.ToString(); } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs index 218a84bb0..ba4f7ebaa 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs @@ -31,7 +31,7 @@ void AssertOutput(string expected, AstNode node, CSharpFormattingOptions policy policy = FormattingOptionsFactory.CreateMono(); StringWriter w = new StringWriter(); w.NewLine = "\n"; - node.AcceptVisitor(new CSharpOutputVisitor(new TextWriterOutputFormatter(w) { IndentationString = "$" }, policy)); + node.AcceptVisitor(new CSharpOutputVisitor(new TextWriterTokenWriter(w) { IndentationString = "$" }, policy)); Assert.AreEqual(expected.Replace("\r", ""), w.ToString()); } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/CreateEnumValueTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/CreateEnumValueTests.cs index b91f1f58b..3521540b7 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/CreateEnumValueTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/CreateEnumValueTests.cs @@ -76,8 +76,73 @@ public void Main () } } "); - } + } + + /// + /// Bug 16663 - Enum constants with Flags are not recognized + /// + [Test] + public void TestBug16663() + { + Test(@" +public enum MyEnum +{ +} + +class Test +{ + static void Main () + { + var e = MyEnum.$NotDefinedYetValue; + } +} +",@" +public enum MyEnum +{ + NotDefinedYetValue +} + +class Test +{ + static void Main () + { + var e = MyEnum.NotDefinedYetValue; + } +} +"); + } + + [Test] + public void TestBug16663_Case2() + { + Test(@" +public enum MyEnum +{ +} + +class Test +{ + static void Main () + { + var e = MyEnum.$NotDefinedYetValue | MyEnum.Foo; + } +} +",@" +public enum MyEnum +{ + NotDefinedYetValue +} + +class Test +{ + static void Main () + { + var e = MyEnum.NotDefinedYetValue | MyEnum.Foo; + } +} +"); } + } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/RemoveBackingStoreTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/RemoveBackingStoreTests.cs index e3ffdfe43..bded3ebed 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/RemoveBackingStoreTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/RemoveBackingStoreTests.cs @@ -174,7 +174,34 @@ class MyClass } "); } - + + + /// + /// Bug 16447 - Convert to Auto Property removes multiple variable if declared inline + /// + [Test] + public void TestBug16447 () + { + Test(@" +public class Foo +{ + int _bpm = 120, _index = 1, _count; + int $Count { + get { return _count; } + set { _count = value; } + } +} +", @" +public class Foo +{ + int _bpm = 120, _index = 1; + int Count { + get; + set; + } +} +"); + } } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/TestRefactoringContext.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/TestRefactoringContext.cs index 40090574d..05b3f95af 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/TestRefactoringContext.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/TestRefactoringContext.cs @@ -137,7 +137,7 @@ public override Task