diff --git a/src/EditorFeatures/Test2/Simplification/TypeNameSimplifierTest.vb b/src/EditorFeatures/Test2/Simplification/TypeNameSimplifierTest.vb index fe7d492b11c25..477f37b305063 100644 --- a/src/EditorFeatures/Test2/Simplification/TypeNameSimplifierTest.vb +++ b/src/EditorFeatures/Test2/Simplification/TypeNameSimplifierTest.vb @@ -1426,6 +1426,42 @@ public class C Test(input, expected) End Sub + + + Public Sub TestCSRemoveThisPreservesTrivia() + Dim input = + + + +class C1 +{ + int _field; + + void M() + { + this /*comment 1*/ . /* comment 2 */ {|SimplifyParent:_field|} /* comment 3 */ = 0; + } +} + + + + + Dim expected = + +class C1 +{ + int _field; + + void M() + { + /*comment 1*/ /* comment 2 */ _field /* comment 3 */ = 0; + } +} + + + Test(input, expected) + End Sub + Public Sub CSharpSimplifyToVarCorrect() diff --git a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs index e027af8cba07b..11e1181c42cf2 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs @@ -773,7 +773,9 @@ private static bool TryReduce( } } - replacementNode = memberAccess.Name.WithLeadingTrivia(memberAccess.GetLeadingTrivia()).WithTrailingTrivia(memberAccess.GetTrailingTrivia()); + replacementNode = memberAccess.Name + .WithLeadingTrivia(memberAccess.GetLeadingTriviaForSimplifiedMemberAccess()) + .WithTrailingTrivia(memberAccess.GetTrailingTrivia()); issueSpan = memberAccess.Expression.Span; if (replacementNode == null) @@ -784,6 +786,24 @@ private static bool TryReduce( return memberAccess.CanReplaceWithReducedName(replacementNode, semanticModel, cancellationToken); } + private static SyntaxTriviaList GetLeadingTriviaForSimplifiedMemberAccess(this MemberAccessExpressionSyntax memberAccess) + { + // We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression. + // However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering + // aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander and use that as a cue to introduce unnecessary blank lines + // etc. around the user's original code. + return memberAccess.GetLeadingTrivia() + .AddRange(memberAccess.Expression.GetTrailingTrivia().WithoutElasticTrivia()) + .AddRange(memberAccess.OperatorToken.LeadingTrivia.WithoutElasticTrivia()) + .AddRange(memberAccess.OperatorToken.TrailingTrivia.WithoutElasticTrivia()) + .AddRange(memberAccess.Name.GetLeadingTrivia().WithoutElasticTrivia()); + } + + private static IEnumerable WithoutElasticTrivia(this IEnumerable list) + { + return list.Where(t => !t.IsElastic()); + } + private static bool InsideCrefReference(ExpressionSyntax expr) { var crefAttribute = expr.FirstAncestorOrSelf(); diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb index 6c16af518aab1..d900d527ebe30 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb @@ -1023,7 +1023,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions .WithIdentifier(VisualBasicSimplificationService.TryEscapeIdentifierToken( memberAccess.Name.Identifier, semanticModel)) _ - .WithLeadingTrivia(memberAccess.GetLeadingTrivia()) + .WithLeadingTrivia(memberAccess.GetLeadingTriviaForSimplifiedMemberAccess()) _ + .WithTrailingTrivia(memberAccess.GetTrailingTrivia()) issueSpan = memberAccess.Expression.Span If memberAccess.CanReplaceWithReducedName(replacementNode, semanticModel, cancellationToken) Then @@ -1040,6 +1041,24 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return False End Function + + Private Function GetLeadingTriviaForSimplifiedMemberAccess(memberAccess As MemberAccessExpressionSyntax) As SyntaxTriviaList + ' We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression. + ' However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering + ' aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander And use that as a cue to introduce unnecessary blank lines + ' etc. around the user's original code. + Return memberAccess.GetLeadingTrivia(). + AddRange(memberAccess.Expression.GetTrailingTrivia().WithoutElasticTrivia()). + AddRange(memberAccess.OperatorToken.LeadingTrivia.WithoutElasticTrivia()). + AddRange(memberAccess.OperatorToken.TrailingTrivia.WithoutElasticTrivia()). + AddRange(memberAccess.Name.GetLeadingTrivia().WithoutElasticTrivia()) + End Function + + + Private Function WithoutElasticTrivia(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) + Return list.Where(Function(t) Not t.IsElastic()) + End Function + Private Function InsideCrefReference(expr As ExpressionSyntax) As Boolean Dim crefAttribute = expr.FirstAncestorOrSelf(Of XmlCrefAttributeSyntax)() Return crefAttribute IsNot Nothing