diff --git a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs index c088bf9a4220c4860314461ad4379f08da945392..252fbb512205398adc3ed8cfaf608acbed8d7812 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs @@ -18,9 +18,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.Introd public class IntroduceVariableTests : AbstractCSharpCodeActionTest { protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace) - { - return new IntroduceVariableCodeRefactoringProvider(); - } + => new IntroduceVariableCodeRefactoringProvider(); private readonly CodeStyleOption onWithInfo = new CodeStyleOption(true, NotificationOption.Suggestion); @@ -3872,8 +3870,8 @@ public async Task ElementOfTuple() var i = (1, [|""hello""|]).ToString(); }"; -var expected = -@"class C + var expected = + @"class C { private const string {|Rename:V|} = ""hello""; var i = (1, V).ToString(); @@ -4019,5 +4017,40 @@ void Foo() withScriptOption: true) ); } + + [WorkItem(11777, "https://github.com/dotnet/roslyn/issues/11777")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task TestGenerateLocalConflictingName1() + { + await TestAsync( +@"class Program +{ + class MySpan { public int Start { get; } public int End { get; } } + void Method(MySpan span) + { + int pos = span.Start; + while (pos < [|span.End|]) + { + int spanEnd = span.End; + int end = pos; + } + } +}", +@" +class Program +{ + class MySpan { public int Start { get; } public int End { get; } } + void Method(MySpan span) + { + int pos = span.Start; + int {|Rename:end1|} = span.End; + while (pos < end1) + { + int spanEnd = span.End; + int end = pos; + } + } +}"); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateVariable/GenerateVariableTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateVariable/GenerateVariableTests.cs index fbad0cef085d0533aaf5d9bf55b68d5e3dffead2..3406b16e7584ea44ed8262825254de2bcb925c4a 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateVariable/GenerateVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateVariable/GenerateVariableTests.cs @@ -7089,4 +7089,4 @@ void Method() withScriptOption: true); } } -} +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs index 44eaadc740930ee9fd22a3422200604f762cbc38..d44bee528df30a054c5b4b441f793f4a50b69bde 100644 --- a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs +++ b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs @@ -28,7 +28,10 @@ internal partial class CSharpIntroduceVariableService bool isConstant, CancellationToken cancellationToken) { - var newLocalNameToken = GenerateUniqueLocalName(document, expression, isConstant, cancellationToken); + var containerToGenerateInto = GetContainerToGenerateInto(document, expression, cancellationToken); + + var newLocalNameToken = GenerateUniqueLocalName( + document, expression, isConstant, containerToGenerateInto, cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var modifiers = isConstant @@ -46,6 +49,29 @@ internal partial class CSharpIntroduceVariableService null, SyntaxFactory.EqualsValueClause(expression.WithoutTrailingTrivia().WithoutLeadingTrivia()))))); + switch (containerToGenerateInto) + { + case BlockSyntax block: + return await IntroduceLocalDeclarationIntoBlockAsync( + document, block, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false); + + case ArrowExpressionClauseSyntax arrowExpression: + return RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( + document, arrowExpression, expression, newLocalName, + declarationStatement, allOccurrences, cancellationToken); + + case LambdaExpressionSyntax lambda: + return IntroduceLocalDeclarationIntoLambda( + document, lambda, expression, newLocalName, declarationStatement, + allOccurrences, cancellationToken); + } + + throw new InvalidOperationException(); + } + + private SyntaxNode GetContainerToGenerateInto( + SemanticDocument document, ExpressionSyntax expression, CancellationToken cancellationToken) + { var anonymousMethodParameters = GetAnonymousMethodParameters(document, expression, cancellationToken); var lambdas = anonymousMethodParameters.SelectMany(p => p.ContainingSymbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax(cancellationToken)).AsEnumerable()) .Where(n => n is ParenthesizedLambdaExpressionSyntax || n is SimpleLambdaExpressionSyntax) @@ -55,27 +81,24 @@ internal partial class CSharpIntroduceVariableService if (parentLambda != null) { - return IntroduceLocalDeclarationIntoLambda( - document, expression, newLocalName, declarationStatement, parentLambda, allOccurrences, cancellationToken); + return parentLambda; } else if (IsInExpressionBodiedMember(expression)) { - return RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( - document, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken); + return expression.GetAncestorOrThis(); } else { - return await IntroduceLocalDeclarationIntoBlockAsync( - document, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false); + return expression.GetAncestorsOrThis().LastOrDefault(); } } private Document IntroduceLocalDeclarationIntoLambda( SemanticDocument document, + SyntaxNode oldLambda, ExpressionSyntax expression, IdentifierNameSyntax newLocalName, LocalDeclarationStatementSyntax declarationStatement, - SyntaxNode oldLambda, bool allOccurrences, CancellationToken cancellationToken) { @@ -190,13 +213,14 @@ private bool CanUseVar(ITypeSymbol typeSymbol) private Document RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( SemanticDocument document, + ArrowExpressionClauseSyntax arrowExpression, ExpressionSyntax expression, NameSyntax newLocalName, LocalDeclarationStatementSyntax declarationStatement, bool allOccurrences, CancellationToken cancellationToken) { - var oldBody = expression.GetAncestorOrThis(); + var oldBody = arrowExpression; var oldParentingNode = oldBody.Parent; var leadingTrivia = oldBody.GetLeadingTrivia() .AddRange(oldBody.ArrowToken.TrailingTrivia); @@ -267,6 +291,7 @@ private bool CanUseVar(ITypeSymbol typeSymbol) private async Task IntroduceLocalDeclarationIntoBlockAsync( SemanticDocument document, + BlockSyntax block, ExpressionSyntax expression, NameSyntax newLocalName, LocalDeclarationStatementSyntax declarationStatement, @@ -275,7 +300,7 @@ private bool CanUseVar(ITypeSymbol typeSymbol) { declarationStatement = declarationStatement.WithAdditionalAnnotations(Formatter.Annotation); - var oldOutermostBlock = expression.GetAncestorsOrThis().LastOrDefault(); + var oldOutermostBlock = block; var matches = FindMatches(document, expression, document, oldOutermostBlock, allOccurrences, cancellationToken); Debug.Assert(matches.Contains(expression)); diff --git a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceQueryLocal.cs b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceQueryLocal.cs index 0559957e631c7dd07d8ab5e117488c98c1b02155..39638a5993718a424cd38ef8201afa4dc6e95744 100644 --- a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceQueryLocal.cs +++ b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceQueryLocal.cs @@ -24,14 +24,17 @@ private static bool IsAnyQueryClause(SyntaxNode node) protected override Task IntroduceQueryLocalAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, CancellationToken cancellationToken) { - var newLocalNameToken = GenerateUniqueLocalName(document, expression, isConstant: false, cancellationToken: cancellationToken); + var oldOutermostQuery = expression.GetAncestorsOrThis().LastOrDefault(); + + var newLocalNameToken = GenerateUniqueLocalName( + document, expression, isConstant: false, + container: oldOutermostQuery, cancellationToken: cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var letClause = SyntaxFactory.LetClause( - newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), - expression).WithAdditionalAnnotations(Formatter.Annotation); + newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), + expression).WithAdditionalAnnotations(Formatter.Annotation); - var oldOutermostQuery = expression.GetAncestorsOrThis().LastOrDefault(); var matches = FindMatches(document, expression, document, oldOutermostQuery, allOccurrences, cancellationToken); var innermostClauses = new HashSet( matches.Select(expr => expr.GetAncestorsOrThis().First(IsAnyQueryClause))); diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs index edad8fcf092cbcfd533fc1e81bb7b861ca58c9bc..49876e5e52c5429709d32dc91e776f263003d971 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs @@ -20,9 +20,6 @@ internal abstract class AbstractIntroduceVariableCodeAction : CodeAction private readonly TExpressionSyntax _expression; private readonly SemanticDocument _document; private readonly TService _service; - private readonly string _title; - - private static readonly Regex s_newlinePattern = new Regex(@"[\r\n]+"); internal AbstractIntroduceVariableCodeAction( TService service, @@ -40,13 +37,10 @@ internal abstract class AbstractIntroduceVariableCodeAction : CodeAction _isConstant = isConstant; _isLocal = isLocal; _isQueryLocal = isQueryLocal; - _title = CreateDisplayText(expression); + Title = CreateDisplayText(expression); } - public override string Title - { - get { return _title; } - } + public override string Title { get; } protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) { @@ -79,10 +73,7 @@ private async Task IntroduceFieldAsync(CancellationToken cancellationT private string CreateDisplayText(TExpressionSyntax expression) { var singleLineExpression = _document.Project.LanguageServices.GetService().ConvertToSingleLine(expression); - var nodeString = singleLineExpression.ToFullString().Trim(); - - // prevent the display string from spanning multiple lines - nodeString = s_newlinePattern.Replace(nodeString, " "); + var nodeString = singleLineExpression.ToString(); // prevent the display string from being too long const int MaxLength = 40; diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs index 5422739111bcb4c8fb2f9c37a73aac4a3410ac0a..8ad8466d787b57b80ce7cec787943533222be60a 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs @@ -216,19 +216,55 @@ private CodeAction CreateAction(State state, bool allOccurrences, bool isConstan SemanticDocument document, TExpressionSyntax expression, bool isConstant, + SyntaxNode container, CancellationToken cancellationToken) { - var syntaxFacts = document.Project.LanguageServices.GetService(); - var semanticFacts = document.Project.LanguageServices.GetService(); + var syntaxFacts = document.Document.GetLanguageService(); + var semanticFacts = document.Document.GetLanguageService(); var semanticModel = document.SemanticModel; + var existingSymbols = GetExistingSymbols(semanticModel, container, cancellationToken); + var baseName = semanticFacts.GenerateNameForExpression(semanticModel, expression, capitalize: isConstant); - var reservedNames = semanticModel.LookupSymbols(expression.SpanStart).Select(s => s.Name); + var reservedNames = semanticModel.LookupSymbols(expression.SpanStart) + .Select(s => s.Name) + .Concat(existingSymbols.Select(s => s.Name)); return syntaxFacts.ToIdentifierToken( NameGenerator.EnsureUniqueness(baseName, reservedNames, syntaxFacts.IsCaseSensitive)); } + private static HashSet GetExistingSymbols( + SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken) + { + var symbols = new HashSet(); + if (container != null) + { + GetExistingSymbols(semanticModel, container, symbols, cancellationToken); + } + + return symbols; + } + + private static void GetExistingSymbols( + SemanticModel semanticModel, SyntaxNode node, + HashSet symbols, CancellationToken cancellationToken) + { + var symbol = semanticModel.GetDeclaredSymbol(node, cancellationToken); + if (symbol != null) + { + symbols.Add(symbol); + } + + foreach (var child in node.ChildNodesAndTokens()) + { + if (child.IsNode) + { + GetExistingSymbols(semanticModel, child.AsNode(), symbols, cancellationToken); + } + } + } + protected ISet FindMatches( SemanticDocument originalDocument, TExpressionSyntax expressionInOriginal, diff --git a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceField.vb b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceField.vb index 8652a55dbbf3182cf383b067dcabc0e6f44509f1..cb29e482faf4aafc5225eeda7e782bdc190a62c3 100644 --- a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceField.vb +++ b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceField.vb @@ -10,23 +10,26 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable Friend Partial Class VisualBasicIntroduceVariableService - Protected Overrides Async Function IntroduceFieldAsync(document As SemanticDocument, - expression As ExpressionSyntax, - allOccurrences As Boolean, - isConstant As Boolean, - cancellationToken As CancellationToken) As Task(Of Tuple(Of Document, SyntaxNode, Integer)) + Protected Overrides Async Function IntroduceFieldAsync( + document As SemanticDocument, + expression As ExpressionSyntax, + allOccurrences As Boolean, + isConstant As Boolean, + cancellationToken As CancellationToken) As Task(Of Tuple(Of Document, SyntaxNode, Integer)) Dim oldTypeDeclaration = expression.GetAncestorOrThis(Of TypeBlockSyntax)() Dim oldType = If(oldTypeDeclaration IsNot Nothing, document.SemanticModel.GetDeclaredSymbol(oldTypeDeclaration.BlockStatement, cancellationToken), Nothing) - Dim newNameToken = GenerateUniqueLocalName(document, expression, isConstant, cancellationToken) + Dim newNameToken = GenerateUniqueLocalName( + document, expression, isConstant, container:=Nothing, + cancellationToken:=cancellationToken) Dim newQualifiedName = SyntaxFactory.SimpleMemberAccessExpression( - expression:=SyntaxFactory.ParseName(oldType.ToNameDisplayString()), - operatorToken:=SyntaxFactory.Token(SyntaxKind.DotToken), - name:=SyntaxFactory.IdentifierName(newNameToken)).WithAdditionalAnnotations(Simplifier.Annotation) + expression:=SyntaxFactory.ParseName(oldType.ToNameDisplayString()), + operatorToken:=SyntaxFactory.Token(SyntaxKind.DotToken), + name:=SyntaxFactory.IdentifierName(newNameToken)).WithAdditionalAnnotations(Simplifier.Annotation) If oldType IsNot Nothing Then Return Await IntroduceFieldIntoTypeAsync( @@ -43,6 +46,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable Dim newRoot = newCompilationUnit.WithMembers( newCompilationUnit.Members.Insert(insertionIndex, newFieldDeclaration)) + Return Tuple.Create(document.Document.WithSyntaxRoot(newRoot), destination, insertionIndex) End If End Function diff --git a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceLocal.vb b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceLocal.vb index 388a262182cf7171cef8a7d38a6c058cd2d776b5..87583801854354931f750424f9ce942130b06e90 100644 --- a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceLocal.vb +++ b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceLocal.vb @@ -17,7 +17,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable isConstant As Boolean, cancellationToken As CancellationToken) As Task(Of Document) - Dim newLocalNameToken = CType(GenerateUniqueLocalName(document, expression, isConstant, cancellationToken), SyntaxToken) + Dim container = GetContainerToGenerateInfo(document, expression, cancellationToken) + Dim newLocalNameToken = GenerateUniqueLocalName( + document, expression, isConstant, container, cancellationToken) Dim newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken) Dim modifier = If(isConstant, SyntaxFactory.Token(SyntaxKind.ConstKeyword), SyntaxFactory.Token(SyntaxKind.DimKeyword)) @@ -37,6 +39,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable declarationStatement = declarationStatement.WithAppendedTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed) End If + If TypeOf container Is SingleLineLambdaExpressionSyntax Then + Return IntroduceLocalDeclarationIntoLambda( + document, DirectCast(container, SingleLineLambdaExpressionSyntax), + expression, newLocalName, declarationStatement, allOccurrences, cancellationToken) + Else + Return Await IntroduceLocalDeclarationIntoBlockAsync( + document, container, expression, newLocalName, + declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(False) + End If + End Function + + Private Function GetContainerToGenerateInfo( + document As SemanticDocument, + expression As ExpressionSyntax, + cancellationToken As CancellationToken) As SyntaxNode + Dim anonymousMethodParameters = GetAnonymousMethodParameters(document, expression, cancellationToken) Dim lambdas = anonymousMethodParameters.SelectMany(Function(p) p.ContainingSymbol.DeclaringSyntaxReferences). Select(Function(r) r.GetSyntax()). @@ -45,23 +63,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable ToSet() Dim parentLambda = GetParentLambda(expression, lambdas) - If parentLambda IsNot Nothing Then - Return IntroduceLocalDeclarationIntoLambda( - document, expression, newLocalName, declarationStatement, parentLambda, allOccurrences, cancellationToken) - Else - Return Await IntroduceLocalDeclarationIntoBlockAsync( - document, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(False) + Return parentLambda End If + + Return expression.GetContainingExecutableBlocks().LastOrDefault() End Function - Private Function IntroduceLocalDeclarationIntoLambda(document As SemanticDocument, - expression As ExpressionSyntax, - newLocalName As IdentifierNameSyntax, - declarationStatement As StatementSyntax, - oldLambda As SingleLineLambdaExpressionSyntax, - allOccurrences As Boolean, - cancellationToken As CancellationToken) As Document + Private Function IntroduceLocalDeclarationIntoLambda( + document As SemanticDocument, + oldLambda As SingleLineLambdaExpressionSyntax, + expression As ExpressionSyntax, + newLocalName As IdentifierNameSyntax, + declarationStatement As StatementSyntax, + allOccurrences As Boolean, + cancellationToken As CancellationToken) As Document + Dim oldBody = DirectCast(oldLambda.Body, ExpressionSyntax) Dim rewrittenBody = Rewrite( @@ -118,6 +135,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable Private Async Function IntroduceLocalDeclarationIntoBlockAsync( document As SemanticDocument, + container As SyntaxNode, expression As ExpressionSyntax, newLocalName As NameSyntax, localDeclaration As LocalDeclarationStatementSyntax, @@ -127,7 +145,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable Dim localAnnotation = New SyntaxAnnotation() localDeclaration = localDeclaration.WithAdditionalAnnotations(Formatter.Annotation, localAnnotation) - Dim oldOutermostBlock = expression.GetContainingExecutableBlocks().LastOrDefault() + Dim oldOutermostBlock = container If oldOutermostBlock.IsSingleLineExecutableBlock() Then oldOutermostBlock = oldOutermostBlock.Parent End If diff --git a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceQueryLocal.vb b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceQueryLocal.vb index bbefc0851ab3ddc095ca4d04afbb4cd10efb8233..11184bf1ee8800241e15700ce02d0ea5a1812b88 100644 --- a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceQueryLocal.vb +++ b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceVariableService_IntroduceQueryLocal.vb @@ -8,12 +8,17 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable Friend Partial Class VisualBasicIntroduceVariableService - Protected Overrides Function IntroduceQueryLocalAsync(document As SemanticDocument, - expression As ExpressionSyntax, - allOccurrences As Boolean, - cancellationToken As CancellationToken) As Task(Of Document) + Protected Overrides Function IntroduceQueryLocalAsync( + document As SemanticDocument, + expression As ExpressionSyntax, + allOccurrences As Boolean, + cancellationToken As CancellationToken) As Task(Of Document) - Dim newLocalNameToken = GenerateUniqueLocalName(document, expression, isConstant:=False, cancellationToken:=cancellationToken) + Dim oldOutermostQuery = expression.GetAncestorsOrThis(Of QueryExpressionSyntax)().LastOrDefault() + + Dim newLocalNameToken = GenerateUniqueLocalName( + document, expression, isConstant:=False, + container:=oldOutermostQuery, cancellationToken:=cancellationToken) Dim newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken) Dim letClause = SyntaxFactory.LetClause( @@ -22,7 +27,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable SyntaxFactory.ModifiedIdentifier(newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()))), expression)).WithAdditionalAnnotations(Formatter.Annotation) - Dim oldOutermostQuery = expression.GetAncestorsOrThis(Of QueryExpressionSyntax)().LastOrDefault() Dim matches = FindMatches(document, expression, document, oldOutermostQuery, allOccurrences, cancellationToken) Dim innermostClauses = New HashSet(Of QueryClauseSyntax)( matches.Select(Function(expr) expr.GetAncestor(Of QueryClauseSyntax)())) diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.SingleLineRewriter.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.SingleLineRewriter.cs index 4d247970c25980ed09ef081603b4c319184d2a5b..097feaf5b5d2cd41b6332d957796056b293be756 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.SingleLineRewriter.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.SingleLineRewriter.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; +using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -12,12 +13,14 @@ internal partial class SyntaxNodeExtensions { internal class SingleLineRewriter : CSharpSyntaxRewriter { - private bool useElasticTrivia; + private static readonly Regex s_newlinePattern = new Regex(@"[\r\n]+"); + + private readonly bool _useElasticTrivia; private bool _lastTokenEndedInWhitespace; public SingleLineRewriter(bool useElasticTrivia) { - this.useElasticTrivia = useElasticTrivia; + this._useElasticTrivia = useElasticTrivia; } public override SyntaxToken VisitToken(SyntaxToken token) @@ -28,7 +31,7 @@ public override SyntaxToken VisitToken(SyntaxToken token) } else if (token.LeadingTrivia.Count > 0) { - if (useElasticTrivia) + if (_useElasticTrivia) { token = token.WithLeadingTrivia(SyntaxFactory.ElasticSpace); } @@ -40,7 +43,7 @@ public override SyntaxToken VisitToken(SyntaxToken token) if (token.TrailingTrivia.Count > 0) { - if (useElasticTrivia) + if (_useElasticTrivia) { token = token.WithTrailingTrivia(SyntaxFactory.ElasticSpace); } @@ -48,6 +51,7 @@ public override SyntaxToken VisitToken(SyntaxToken token) { token = token.WithTrailingTrivia(SyntaxFactory.Space); } + _lastTokenEndedInWhitespace = true; } else @@ -55,6 +59,20 @@ public override SyntaxToken VisitToken(SyntaxToken token) _lastTokenEndedInWhitespace = false; } + if (token.Kind() == SyntaxKind.StringLiteralToken || + token.Kind() == SyntaxKind.InterpolatedStringTextToken) + { + if (s_newlinePattern.IsMatch(token.Text)) + { + var newText = s_newlinePattern.Replace(token.Text, " "); + token = SyntaxFactory.Token( + token.LeadingTrivia, + token.Kind(), + newText, newText, + token.TrailingTrivia); + } + } + return token; } } diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index b43f996ed8ed4f3811b679244da20dd90430f0a1..02a02b6c60c18d0abd8ee867519cc577fe558fc1 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -714,9 +714,7 @@ public bool IsElementAccessExpression(SyntaxNode node) } public SyntaxNode ConvertToSingleLine(SyntaxNode node, bool useElasticTrivia = false) - { - return node.ConvertToSingleLine(useElasticTrivia); - } + => node.ConvertToSingleLine(useElasticTrivia); public SyntaxToken ToIdentifierToken(string name) { diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/SingleLineRewriter.vb b/src/Workspaces/VisualBasic/Portable/Extensions/SingleLineRewriter.vb index 70dfe41ab19885680f22b6c6bef60169e81c000d..09006292bd50db1c70408446a9afeb7c8e9f635b 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/SingleLineRewriter.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/SingleLineRewriter.vb @@ -12,29 +12,31 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Friend Class SingleLineRewriter Inherits VisualBasicSyntaxRewriter - Private useElasticTrivia As Boolean + Private ReadOnly _useElasticTrivia As Boolean Private _lastTokenEndedInWhitespace As Boolean Public Sub New(Optional useElasticTrivia As Boolean = False) - Me.useElasticTrivia = useElasticTrivia + _useElasticTrivia = useElasticTrivia End Sub Public Overrides Function VisitToken(token As SyntaxToken) As SyntaxToken If _lastTokenEndedInWhitespace Then token = token.WithLeadingTrivia(Enumerable.Empty(Of SyntaxTrivia)()) ElseIf token.LeadingTrivia.Count > 0 Then - If useElasticTrivia Then + If _useElasticTrivia Then token = token.WithLeadingTrivia(SyntaxFactory.ElasticSpace) Else token = token.WithLeadingTrivia(SyntaxFactory.Space) End If End If + If token.TrailingTrivia.Count > 0 Then - If useElasticTrivia Then + If _useElasticTrivia Then token = token.WithTrailingTrivia(SyntaxFactory.ElasticSpace) Else token = token.WithTrailingTrivia(SyntaxFactory.Space) End If + _lastTokenEndedInWhitespace = True Else _lastTokenEndedInWhitespace = False