diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs index 61c32dd8c8f28..fc9efabda32d9 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.CSharp.ExtractMethod; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.ExtractMethod; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -23,7 +24,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ExtractMethod; [UseExportProvider] -public class ExtractMethodBase +public abstract class ExtractMethodBase { protected static async Task ExpectExtractMethodToFailAsync( [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string codeWithMarker, string[] features = null) @@ -128,19 +129,8 @@ protected static async Task ExtractMethodAsync( CodeCleanupOptions = await document.GetCodeCleanupOptionsAsync(CancellationToken.None), }; - var semanticDocument = await SemanticDocument.CreateAsync(document, CancellationToken.None); - var validator = new CSharpSelectionValidator(semanticDocument, testDocument.SelectedSpans.Single(), localFunction); - - var (selectedCode, status) = await validator.GetValidSelectionAsync(CancellationToken.None); - if (!succeed && status.Failed) - return null; - - Assert.NotNull(selectedCode); - - // extract method - var extractor = new CSharpMethodExtractor(selectedCode, options, localFunction); - var result = extractor.ExtractMethod(status, CancellationToken.None); - Assert.NotNull(result); + var result = await ExtractMethodService.ExtractMethodAsync( + document, testDocument.SelectedSpans.Single(), localFunction, options, CancellationToken.None); // If the test expects us to succeed, validate that we did. If it expects us to fail, ensure we either // failed or produced a message the user will have to confirm to continue. @@ -174,7 +164,8 @@ protected static async Task TestSelectionAsync( Assert.NotNull(document); var semanticDocument = await SemanticDocument.CreateAsync(document, CancellationToken.None); - var validator = new CSharpSelectionValidator(semanticDocument, textSpanOverride ?? namedSpans["b"].Single(), localFunction: false); + + var validator = new CSharpExtractMethodService.CSharpSelectionValidator(semanticDocument, textSpanOverride ?? namedSpans["b"].Single(), localFunction: false); var (result, status) = await validator.GetValidSelectionAsync(CancellationToken.None); if (expectedFail) @@ -203,7 +194,7 @@ protected static async Task IterateAllAsync( foreach (var node in iterator) { - var validator = new CSharpSelectionValidator(semanticDocument, node.Span, localFunction: false); + var validator = new CSharpExtractMethodService.CSharpSelectionValidator(semanticDocument, node.Span, localFunction: false); var (_, status) = await validator.GetValidSelectionAsync(CancellationToken.None); // check the obvious case diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.cs index 83bddcd29ba07..ebc2950154888 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.cs @@ -1113,7 +1113,7 @@ public static void Main() p1 = NewMethod(p2); } - private static object NewMethod(object p2) + private static global::System.Object NewMethod(global::System.Object p2) { return p2; } diff --git a/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.vb b/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.vb index 99ff3e546669a..d0787bbf296df 100644 --- a/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.vb @@ -95,7 +95,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ExtractMethod Assert.NotNull(document) Dim sdocument = Await SemanticDocument.CreateAsync(document, CancellationToken.None) - Dim validator = New VisualBasicSelectionValidator(sdocument, snapshotSpan.Span.ToTextSpan()) + Dim validator = New VisualBasicExtractMethodService.VisualBasicSelectionValidator(sdocument, snapshotSpan.Span.ToTextSpan()) Dim tuple = Await validator.GetValidSelectionAsync(CancellationToken.None) Dim selectedCode = tuple.Item1 @@ -109,7 +109,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ExtractMethod Await document.GetCodeGenerationOptionsAsync(CancellationToken.None), Await document.GetCodeCleanupOptionsAsync(CancellationToken.None)) - Dim extractor = New VisualBasicMethodExtractor(selectedCode, extractGenerationOptions) + Dim extractor = New VisualBasicExtractMethodService.VisualBasicMethodExtractor(selectedCode, extractGenerationOptions) Dim result = extractor.ExtractMethod(status, CancellationToken.None) Assert.NotNull(result) @@ -137,7 +137,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ExtractMethod Assert.NotNull(document) Dim sdocument = Await SemanticDocument.CreateAsync(document, CancellationToken.None) - Dim validator = New VisualBasicSelectionValidator(sdocument, namedSpans("b").Single()) + Dim validator = New VisualBasicExtractMethodService.VisualBasicSelectionValidator(sdocument, namedSpans("b").Single()) Dim tuple = Await validator.GetValidSelectionAsync(CancellationToken.None) Dim result = tuple.Item1 Dim status = tuple.Item2 @@ -172,7 +172,7 @@ End Class For Each node In iterator Try - Dim validator = New VisualBasicSelectionValidator(sdocument, node.Span) + Dim validator = New VisualBasicExtractMethodService.VisualBasicSelectionValidator(sdocument, node.Span) Dim tuple = Await validator.GetValidSelectionAsync(CancellationToken.None) Dim result = tuple.Item1 Dim status = tuple.Item2 diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index 59396db67f131..8171ec91bb9d0 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -151,9 +151,6 @@ Remove 'this' qualification - - Cannot determine valid range of statements to extract - Selection does not contain a valid node @@ -181,9 +178,6 @@ The selected code is inside an unsafe context. - - No valid statement range to extract - deprecated diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpExtractMethodService.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpExtractMethodService.cs index 3d44fda77430b..dede434239b5f 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpExtractMethodService.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpExtractMethodService.cs @@ -15,10 +15,11 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; [ExportLanguageService(typeof(IExtractMethodService), LanguageNames.CSharp)] [method: ImportingConstructor] [method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] -internal sealed class CSharpExtractMethodService() : AbstractExtractMethodService< - CSharpSelectionValidator, - CSharpMethodExtractor, - CSharpSelectionResult, +internal sealed partial class CSharpExtractMethodService() : AbstractExtractMethodService< + CSharpExtractMethodService.CSharpSelectionValidator, + CSharpExtractMethodService.CSharpMethodExtractor, + CSharpExtractMethodService.CSharpSelectionResult, + StatementSyntax, StatementSyntax, ExpressionSyntax> { diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs index 8b72977ad3c72..c91fd6c6d63a8 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -10,79 +11,80 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private sealed class CSharpAnalyzer(CSharpSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken) : Analyzer(selectionResult, localFunction, cancellationToken) + internal sealed partial class CSharpMethodExtractor { - private static readonly HashSet s_nonNoisySyntaxKindSet = [(int)SyntaxKind.WhitespaceTrivia, (int)SyntaxKind.EndOfLineTrivia]; - - public static AnalyzerResult Analyze(CSharpSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken) + private sealed class CSharpAnalyzer(CSharpSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken) : Analyzer(selectionResult, localFunction, cancellationToken) { - var analyzer = new CSharpAnalyzer(selectionResult, localFunction, cancellationToken); - return analyzer.Analyze(); - } + public static AnalyzerResult Analyze(CSharpSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken) + { + var analyzer = new CSharpAnalyzer(selectionResult, localFunction, cancellationToken); + return analyzer.Analyze(); + } - protected override bool TreatOutAsRef - => false; + protected override bool TreatOutAsRef + => false; - protected override bool IsInPrimaryConstructorBaseType() - => this.SelectionResult.GetContainingScopeOf() != null; + protected override bool IsInPrimaryConstructorBaseType() + => this.SelectionResult.GetContainingScopeOf() != null; - protected override VariableInfo CreateFromSymbol( - ISymbol symbol, ITypeSymbol type, VariableStyle style, bool variableDeclared) - { - return CreateFromSymbolCommon(symbol, type, style, s_nonNoisySyntaxKindSet); - } + protected override VariableInfo CreateFromSymbol( + ISymbol symbol, ITypeSymbol type, VariableStyle style, bool variableDeclared) + { + return CreateFromSymbolCommon(symbol, type, style); + } - protected override ITypeSymbol? GetRangeVariableType(SemanticModel model, IRangeVariableSymbol symbol) - { - var info = model.GetSpeculativeTypeInfo(SelectionResult.FinalSpan.Start, SyntaxFactory.ParseName(symbol.Name), SpeculativeBindingOption.BindAsExpression); - if (info.Type is IErrorTypeSymbol) - return null; + protected override ITypeSymbol? GetRangeVariableType(IRangeVariableSymbol symbol) + { + var info = this.SemanticModel.GetSpeculativeTypeInfo(SelectionResult.FinalSpan.Start, SyntaxFactory.ParseName(symbol.Name), SpeculativeBindingOption.BindAsExpression); + if (info.Type is IErrorTypeSymbol) + return null; - return info.Type == null || info.Type.SpecialType == SpecialType.System_Object - ? info.Type - : info.ConvertedType; - } + return info.Type == null || info.Type.SpecialType == SpecialType.System_Object + ? info.Type + : info.ConvertedType; + } - protected override bool ContainsReturnStatementInSelectedCode(IEnumerable jumpOutOfRegionStatements) - => jumpOutOfRegionStatements.Where(n => n is ReturnStatementSyntax).Any(); + protected override bool ContainsReturnStatementInSelectedCode(ImmutableArray exitPoints) + => exitPoints.Any(n => n is ReturnStatementSyntax); - protected override bool ReadOnlyFieldAllowed() - { - var scope = SelectionResult.GetContainingScopeOf(); - return scope == null; - } + protected override bool ReadOnlyFieldAllowed() + { + var scope = SelectionResult.GetContainingScopeOf(); + return scope == null; + } - protected override bool IsReadOutside(ISymbol symbol, HashSet readOutsideMap) - { - if (!base.IsReadOutside(symbol, readOutsideMap)) - return false; + protected override bool IsReadOutside(ISymbol symbol, HashSet readOutsideMap) + { + if (!base.IsReadOutside(symbol, readOutsideMap)) + return false; - // Special case `using var v = ...` where the selection grabs the last statement that follows the local - // declaration. The compiler here considers the local variable 'read outside' since it makes it to the - // implicit 'dispose' call that comes after the last statement. However, as that implicit dispose would - // move if we move the `using var v` entirely into the new method, then it's still safe to move as there's - // no actual "explicit user read" that happens in the outer caller at all. - if (!this.SelectionResult.SelectionInExpression && - symbol is ILocalSymbol { IsUsing: true, DeclaringSyntaxReferences: [var reference] } && - reference.GetSyntax(this.CancellationToken) is VariableDeclaratorSyntax - { - Parent: VariableDeclarationSyntax + // Special case `using var v = ...` where the selection grabs the last statement that follows the local + // declaration. The compiler here considers the local variable 'read outside' since it makes it to the + // implicit 'dispose' call that comes after the last statement. However, as that implicit dispose would + // move if we move the `using var v` entirely into the new method, then it's still safe to move as there's + // no actual "explicit user read" that happens in the outer caller at all. + if (!this.SelectionResult.IsExtractMethodOnExpression && + symbol is ILocalSymbol { IsUsing: true, DeclaringSyntaxReferences: [var reference] } && + reference.GetSyntax(this.CancellationToken) is VariableDeclaratorSyntax { - Parent: LocalDeclarationStatementSyntax + Parent: VariableDeclarationSyntax { - Parent: BlockSyntax { Statements: [.., var lastBlockStatement] }, - }, - } - }) - { - var lastStatement = this.SelectionResult.GetLastStatement(); - if (lastStatement == lastBlockStatement) - return false; - } + Parent: LocalDeclarationStatementSyntax + { + Parent: BlockSyntax { Statements: [.., var lastBlockStatement] }, + }, + } + }) + { + var lastStatement = this.SelectionResult.GetLastStatement(); + if (lastStatement == lastBlockStatement) + return false; + } - return true; + return true; + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs index 4fcb6f6700c7c..f325648475da2 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs @@ -15,430 +15,430 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private abstract partial class CSharpCodeGenerator + internal sealed partial class CSharpMethodExtractor { - private sealed class CallSiteContainerRewriter : CSharpSyntaxRewriter + private abstract partial class CSharpCodeGenerator { - private readonly SyntaxNode _outmostCallSiteContainer; - private readonly HashSet _variableToRemoveMap; - private readonly SyntaxNode _firstStatementOrFieldToReplace; - private readonly SyntaxNode _lastStatementOrFieldToReplace; - private readonly ImmutableArray _statementsOrMemberOrAccessorToInsert; - - public CallSiteContainerRewriter( - SyntaxNode outmostCallSiteContainer, - HashSet variableToRemoveMap, - SyntaxNode firstStatementOrFieldToReplace, - SyntaxNode lastStatementOrFieldToReplace, - ImmutableArray statementsOrFieldToInsert) + private sealed class CallSiteContainerRewriter : CSharpSyntaxRewriter { - Contract.ThrowIfNull(outmostCallSiteContainer); - Contract.ThrowIfNull(variableToRemoveMap); - Contract.ThrowIfNull(firstStatementOrFieldToReplace); - Contract.ThrowIfNull(lastStatementOrFieldToReplace); - Contract.ThrowIfTrue(statementsOrFieldToInsert.IsDefaultOrEmpty); - - _outmostCallSiteContainer = outmostCallSiteContainer; - - _variableToRemoveMap = variableToRemoveMap; - _firstStatementOrFieldToReplace = firstStatementOrFieldToReplace; - _lastStatementOrFieldToReplace = lastStatementOrFieldToReplace; - _statementsOrMemberOrAccessorToInsert = statementsOrFieldToInsert; - - Contract.ThrowIfFalse(_firstStatementOrFieldToReplace.Parent == _lastStatementOrFieldToReplace.Parent - || CSharpSyntaxFacts.Instance.AreStatementsInSameContainer(_firstStatementOrFieldToReplace, _lastStatementOrFieldToReplace)); - } + private readonly SyntaxNode _outmostCallSiteContainer; + private readonly HashSet _variableToRemoveMap; + private readonly SyntaxNode _firstStatementOrFieldToReplace; + private readonly SyntaxNode _lastStatementOrFieldToReplace; + private readonly ImmutableArray _statementsOrMemberOrAccessorToInsert; + + public CallSiteContainerRewriter( + SyntaxNode outmostCallSiteContainer, + HashSet variableToRemoveMap, + SyntaxNode firstStatementOrFieldToReplace, + SyntaxNode lastStatementOrFieldToReplace, + ImmutableArray statementsOrFieldToInsert) + { + Contract.ThrowIfNull(outmostCallSiteContainer); + Contract.ThrowIfNull(variableToRemoveMap); + Contract.ThrowIfNull(firstStatementOrFieldToReplace); + Contract.ThrowIfNull(lastStatementOrFieldToReplace); + Contract.ThrowIfTrue(statementsOrFieldToInsert.IsDefaultOrEmpty); + + _outmostCallSiteContainer = outmostCallSiteContainer; + + _variableToRemoveMap = variableToRemoveMap; + _firstStatementOrFieldToReplace = firstStatementOrFieldToReplace; + _lastStatementOrFieldToReplace = lastStatementOrFieldToReplace; + _statementsOrMemberOrAccessorToInsert = statementsOrFieldToInsert; + } - public SyntaxNode Generate() - => Visit(_outmostCallSiteContainer); + public SyntaxNode Generate() + => Visit(_outmostCallSiteContainer); - private SyntaxNode ContainerOfStatementsOrFieldToReplace => _firstStatementOrFieldToReplace.Parent; + private SyntaxNode ContainerOfStatementsOrFieldToReplace => _firstStatementOrFieldToReplace.Parent; - public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node) - { - node = (LocalDeclarationStatementSyntax)base.VisitLocalDeclarationStatement(node); - var list = new List(); - var triviaList = new List(); - // go through each var decls in decl statement - foreach (var variable in node.Declaration.Variables) + public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node) { - if (_variableToRemoveMap.HasSyntaxAnnotation(variable)) + node = (LocalDeclarationStatementSyntax)base.VisitLocalDeclarationStatement(node); + var list = new List(); + var triviaList = new List(); + // go through each var decls in decl statement + foreach (var variable in node.Declaration.Variables) { - // if it had initialization, it shouldn't reach here. - Contract.ThrowIfFalse(variable.Initializer == null); + if (_variableToRemoveMap.HasSyntaxAnnotation(variable)) + { + // if it had initialization, it shouldn't reach here. + Contract.ThrowIfFalse(variable.Initializer == null); + + // we don't remove trivia around tokens we remove + triviaList.AddRange(variable.GetLeadingTrivia()); + triviaList.AddRange(variable.GetTrailingTrivia()); + continue; + } + + if (triviaList.Count > 0) + { + list.Add(variable.WithPrependedLeadingTrivia(triviaList)); + triviaList.Clear(); + continue; + } + + list.Add(variable); + } - // we don't remove trivia around tokens we remove - triviaList.AddRange(variable.GetLeadingTrivia()); - triviaList.AddRange(variable.GetTrailingTrivia()); - continue; + if (list.Count == 0) + { + // nothing has survived. remove this from the list + if (triviaList.Count == 0) + { + return null; + } + + // well, there are trivia associated with the node. + // we can't just delete the node since then, we will lose + // the trivia. unfortunately, it is not easy to attach the trivia + // to next token. for now, create an empty statement and associate the + // trivia to the statement + + // TODO : think about a way to move the trivia to next token. + return SyntaxFactory.EmptyStatement(SyntaxFactory.Token([.. triviaList], SyntaxKind.SemicolonToken, [SyntaxFactory.ElasticMarker])); } - if (triviaList.Count > 0) + if (list.Count == node.Declaration.Variables.Count) { - list.Add(variable.WithPrependedLeadingTrivia(triviaList)); - triviaList.Clear(); - continue; + // nothing has changed, return as it is + return node; } - list.Add(variable); + // TODO : fix how it manipulate trivia later + + // if there is left over syntax trivia, it will be attached to leading trivia + // of semicolon + return + SyntaxFactory.LocalDeclarationStatement( + node.Modifiers, + SyntaxFactory.VariableDeclaration( + node.Declaration.Type, + [.. list]), + node.SemicolonToken.WithPrependedLeadingTrivia(triviaList)); } - if (list.Count == 0) + // for every kind of extract methods + public override SyntaxNode VisitBlock(BlockSyntax node) { - // nothing has survived. remove this from the list - if (triviaList.Count == 0) + if (node != ContainerOfStatementsOrFieldToReplace) { - return null; + // make sure we visit nodes under the block + return base.VisitBlock(node); } - // well, there are trivia associated with the node. - // we can't just delete the node since then, we will lose - // the trivia. unfortunately, it is not easy to attach the trivia - // to next token. for now, create an empty statement and associate the - // trivia to the statement - - // TODO : think about a way to move the trivia to next token. - return SyntaxFactory.EmptyStatement(SyntaxFactory.Token([.. triviaList], SyntaxKind.SemicolonToken, [SyntaxFactory.ElasticMarker])); + return node.WithStatements([.. VisitList(ReplaceStatements(node.Statements))]); } - if (list.Count == node.Declaration.Variables.Count) + public override SyntaxNode VisitSwitchSection(SwitchSectionSyntax node) { - // nothing has changed, return as it is - return node; - } - - // TODO : fix how it manipulate trivia later - - // if there is left over syntax trivia, it will be attached to leading trivia - // of semicolon - return - SyntaxFactory.LocalDeclarationStatement( - node.Modifiers, - SyntaxFactory.VariableDeclaration( - node.Declaration.Type, - [.. list]), - node.SemicolonToken.WithPrependedLeadingTrivia(triviaList)); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + // make sure we visit nodes under the switch section + return base.VisitSwitchSection(node); + } - // for every kind of extract methods - public override SyntaxNode VisitBlock(BlockSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) - { - // make sure we visit nodes under the block - return base.VisitBlock(node); + return node.WithStatements([.. VisitList(ReplaceStatements(node.Statements))]); } - return node.WithStatements([.. VisitList(ReplaceStatements(node.Statements))]); - } - - public override SyntaxNode VisitSwitchSection(SwitchSectionSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + // only for single statement or expression + public override SyntaxNode VisitLabeledStatement(LabeledStatementSyntax node) { - // make sure we visit nodes under the switch section - return base.VisitSwitchSection(node); - } - - return node.WithStatements([.. VisitList(ReplaceStatements(node.Statements))]); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitLabeledStatement(node); + } - // only for single statement or expression - public override SyntaxNode VisitLabeledStatement(LabeledStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) - { - return base.VisitLabeledStatement(node); + return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); } - return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } - - public override SyntaxNode VisitElseClause(ElseClauseSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitElseClause(ElseClauseSyntax node) { - return base.VisitElseClause(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitElseClause(node); + } - return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - public override SyntaxNode VisitIfStatement(IfStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitIfStatement(IfStatementSyntax node) { - return base.VisitIfStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitIfStatement(node); + } - return node.WithCondition(VisitNode(node.Condition)) - .WithStatement(ReplaceStatementIfNeeded(node.Statement)) - .WithElse(VisitNode(node.Else)); - } + return node.WithCondition(VisitNode(node.Condition)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)) + .WithElse(VisitNode(node.Else)); + } - public override SyntaxNode VisitLockStatement(LockStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitLockStatement(LockStatementSyntax node) { - return base.VisitLockStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitLockStatement(node); + } - return node.WithExpression(VisitNode(node.Expression)) - .WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithExpression(VisitNode(node.Expression)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - public override SyntaxNode VisitFixedStatement(FixedStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitFixedStatement(FixedStatementSyntax node) { - return base.VisitFixedStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitFixedStatement(node); + } - return node.WithDeclaration(VisitNode(node.Declaration)) - .WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithDeclaration(VisitNode(node.Declaration)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - public override SyntaxNode VisitUsingStatement(UsingStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitUsingStatement(UsingStatementSyntax node) { - return base.VisitUsingStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitUsingStatement(node); + } - return node.WithDeclaration(VisitNode(node.Declaration)) - .WithExpression(VisitNode(node.Expression)) - .WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithDeclaration(VisitNode(node.Declaration)) + .WithExpression(VisitNode(node.Expression)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - public override SyntaxNode VisitForEachStatement(ForEachStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitForEachStatement(ForEachStatementSyntax node) { - return base.VisitForEachStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitForEachStatement(node); + } - return node.WithExpression(VisitNode(node.Expression)) - .WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithExpression(VisitNode(node.Expression)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - public override SyntaxNode VisitForEachVariableStatement(ForEachVariableStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitForEachVariableStatement(ForEachVariableStatementSyntax node) { - return base.VisitForEachVariableStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitForEachVariableStatement(node); + } - return node.WithExpression(VisitNode(node.Expression)) - .WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithExpression(VisitNode(node.Expression)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - public override SyntaxNode VisitForStatement(ForStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitForStatement(ForStatementSyntax node) { - return base.VisitForStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitForStatement(node); + } - return node.WithDeclaration(VisitNode(node.Declaration)) - .WithInitializers(VisitList(node.Initializers)) - .WithCondition(VisitNode(node.Condition)) - .WithIncrementors(VisitList(node.Incrementors)) - .WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithDeclaration(VisitNode(node.Declaration)) + .WithInitializers(VisitList(node.Initializers)) + .WithCondition(VisitNode(node.Condition)) + .WithIncrementors(VisitList(node.Incrementors)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - public override SyntaxNode VisitDoStatement(DoStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitDoStatement(DoStatementSyntax node) { - return base.VisitDoStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitDoStatement(node); + } - return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)) - .WithCondition(VisitNode(node.Condition)); - } + return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)) + .WithCondition(VisitNode(node.Condition)); + } - public override SyntaxNode VisitWhileStatement(WhileStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitWhileStatement(WhileStatementSyntax node) { - return base.VisitWhileStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitWhileStatement(node); + } - return node.WithCondition(VisitNode(node.Condition)) - .WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithCondition(VisitNode(node.Condition)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - private TNode VisitNode(TNode node) where TNode : SyntaxNode - => (TNode)Visit(node); + private TNode VisitNode(TNode node) where TNode : SyntaxNode + => (TNode)Visit(node); - private StatementSyntax ReplaceStatementIfNeeded(StatementSyntax statement) - { - Contract.ThrowIfNull(statement); + private StatementSyntax ReplaceStatementIfNeeded(StatementSyntax statement) + { + Contract.ThrowIfNull(statement); - // if all three same - if ((statement != _firstStatementOrFieldToReplace) || (_firstStatementOrFieldToReplace != _lastStatementOrFieldToReplace)) - return statement; + // if all three same + if ((statement != _firstStatementOrFieldToReplace) || (_firstStatementOrFieldToReplace != _lastStatementOrFieldToReplace)) + return statement; - // replace one statement with another - if (_statementsOrMemberOrAccessorToInsert.Length == 1) - return _statementsOrMemberOrAccessorToInsert.Cast().Single(); + // replace one statement with another + if (_statementsOrMemberOrAccessorToInsert.Length == 1) + return _statementsOrMemberOrAccessorToInsert.Cast().Single(); - // replace one statement with multiple statements (see bug # 6310) - var statements = _statementsOrMemberOrAccessorToInsert.CastArray(); - return SyntaxFactory.Block(statements); - } + // replace one statement with multiple statements (see bug # 6310) + var statements = _statementsOrMemberOrAccessorToInsert.CastArray(); + return SyntaxFactory.Block(statements); + } - private SyntaxList ReplaceList(SyntaxList list) - where TSyntax : SyntaxNode - { - // okay, this visit contains the statement - var newList = new List(list); + private SyntaxList ReplaceList(SyntaxList list) + where TSyntax : SyntaxNode + { + // okay, this visit contains the statement + var newList = new List(list); - var firstIndex = newList.FindIndex(s => s == _firstStatementOrFieldToReplace); - Contract.ThrowIfFalse(firstIndex >= 0); + var firstIndex = newList.FindIndex(s => s == _firstStatementOrFieldToReplace); + Contract.ThrowIfFalse(firstIndex >= 0); - var lastIndex = newList.FindIndex(s => s == _lastStatementOrFieldToReplace); - Contract.ThrowIfFalse(lastIndex >= 0); + var lastIndex = newList.FindIndex(s => s == _lastStatementOrFieldToReplace); + Contract.ThrowIfFalse(lastIndex >= 0); - Contract.ThrowIfFalse(firstIndex <= lastIndex); + Contract.ThrowIfFalse(firstIndex <= lastIndex); - // remove statement that must be removed - newList.RemoveRange(firstIndex, lastIndex - firstIndex + 1); + // remove statement that must be removed + newList.RemoveRange(firstIndex, lastIndex - firstIndex + 1); - // add new statements to replace - newList.InsertRange(firstIndex, _statementsOrMemberOrAccessorToInsert.Cast()); + // add new statements to replace + newList.InsertRange(firstIndex, _statementsOrMemberOrAccessorToInsert.Cast()); - return [.. newList]; - } + return [.. newList]; + } - private SyntaxList ReplaceStatements(SyntaxList statements) - => ReplaceList(statements); + private SyntaxList ReplaceStatements(SyntaxList statements) + => ReplaceList(statements); - private SyntaxList ReplaceAccessors(SyntaxList accessors) - => ReplaceList(accessors); + private SyntaxList ReplaceAccessors(SyntaxList accessors) + => ReplaceList(accessors); - private SyntaxList ReplaceMembers(SyntaxList members, bool global) - { - // okay, this visit contains the statement - var newMembers = new List(members); + private SyntaxList ReplaceMembers(SyntaxList members, bool global) + { + // okay, this visit contains the statement + var newMembers = new List(members); - var firstMemberIndex = newMembers.FindIndex(s => s == (global ? _firstStatementOrFieldToReplace.Parent : _firstStatementOrFieldToReplace)); - Contract.ThrowIfFalse(firstMemberIndex >= 0); + var firstMemberIndex = newMembers.FindIndex(s => s == (global ? _firstStatementOrFieldToReplace.Parent : _firstStatementOrFieldToReplace)); + Contract.ThrowIfFalse(firstMemberIndex >= 0); - var lastMemberIndex = newMembers.FindIndex(s => s == (global ? _lastStatementOrFieldToReplace.Parent : _lastStatementOrFieldToReplace)); - Contract.ThrowIfFalse(lastMemberIndex >= 0); + var lastMemberIndex = newMembers.FindIndex(s => s == (global ? _lastStatementOrFieldToReplace.Parent : _lastStatementOrFieldToReplace)); + Contract.ThrowIfFalse(lastMemberIndex >= 0); - Contract.ThrowIfFalse(firstMemberIndex <= lastMemberIndex); + Contract.ThrowIfFalse(firstMemberIndex <= lastMemberIndex); - // remove statement that must be removed - newMembers.RemoveRange(firstMemberIndex, lastMemberIndex - firstMemberIndex + 1); + // remove statement that must be removed + newMembers.RemoveRange(firstMemberIndex, lastMemberIndex - firstMemberIndex + 1); - // add new statements to replace - newMembers.InsertRange(firstMemberIndex, - _statementsOrMemberOrAccessorToInsert.Select(s => global ? SyntaxFactory.GlobalStatement((StatementSyntax)s) : (MemberDeclarationSyntax)s)); + // add new statements to replace + newMembers.InsertRange(firstMemberIndex, + _statementsOrMemberOrAccessorToInsert.Select(s => global ? SyntaxFactory.GlobalStatement((StatementSyntax)s) : (MemberDeclarationSyntax)s)); - return [.. newMembers]; - } + return [.. newMembers]; + } - public override SyntaxNode VisitGlobalStatement(GlobalStatementSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitGlobalStatement(GlobalStatementSyntax node) { - return base.VisitGlobalStatement(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitGlobalStatement(node); + } - return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); - } + return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } - public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) { - return base.VisitInterfaceDeclaration(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitInterfaceDeclaration(node); + } - return GetUpdatedTypeDeclaration(node); - } + return GetUpdatedTypeDeclaration(node); + } - public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node) { - return base.VisitConstructorDeclaration(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitConstructorDeclaration(node); + } - Contract.ThrowIfFalse(_firstStatementOrFieldToReplace == _lastStatementOrFieldToReplace); - return node.WithInitializer((ConstructorInitializerSyntax)_statementsOrMemberOrAccessorToInsert.Single()); - } + Contract.ThrowIfFalse(_firstStatementOrFieldToReplace == _lastStatementOrFieldToReplace); + return node.WithInitializer((ConstructorInitializerSyntax)_statementsOrMemberOrAccessorToInsert.Single()); + } - public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) { - return base.VisitClassDeclaration(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitClassDeclaration(node); + } - return GetUpdatedTypeDeclaration(node); - } + return GetUpdatedTypeDeclaration(node); + } - public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node) { - return base.VisitRecordDeclaration(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitRecordDeclaration(node); + } - return GetUpdatedTypeDeclaration(node); - } + return GetUpdatedTypeDeclaration(node); + } - public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) { - return base.VisitStructDeclaration(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitStructDeclaration(node); + } - return GetUpdatedTypeDeclaration(node); - } + return GetUpdatedTypeDeclaration(node); + } - public override SyntaxNode VisitAccessorList(AccessorListSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) + public override SyntaxNode VisitAccessorList(AccessorListSyntax node) { - return base.VisitAccessorList(node); - } + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitAccessorList(node); + } - var newAccessors = VisitList(ReplaceAccessors(node.Accessors)); - return node.WithAccessors(newAccessors); - } + var newAccessors = VisitList(ReplaceAccessors(node.Accessors)); + return node.WithAccessors(newAccessors); + } - public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace.Parent) + public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) { - // make sure we visit nodes under the block - return base.VisitCompilationUnit(node); - } + if (node != ContainerOfStatementsOrFieldToReplace.Parent) + { + // make sure we visit nodes under the block + return base.VisitCompilationUnit(node); + } - var newMembers = VisitList(ReplaceMembers(node.Members, global: true)); - return node.WithMembers(newMembers); - } + var newMembers = VisitList(ReplaceMembers(node.Members, global: true)); + return node.WithMembers(newMembers); + } - public override SyntaxNode VisitBaseList(BaseListSyntax node) - { - if (node != ContainerOfStatementsOrFieldToReplace) - return base.VisitBaseList(node); + public override SyntaxNode VisitBaseList(BaseListSyntax node) + { + if (node != ContainerOfStatementsOrFieldToReplace) + return base.VisitBaseList(node); - var primaryConstructorBase = (PrimaryConstructorBaseTypeSyntax)_statementsOrMemberOrAccessorToInsert.Single(); - return node.WithTypes(node.Types.Replace(node.Types[0], primaryConstructorBase)); - } + var primaryConstructorBase = (PrimaryConstructorBaseTypeSyntax)_statementsOrMemberOrAccessorToInsert.Single(); + return node.WithTypes(node.Types.Replace(node.Types[0], primaryConstructorBase)); + } - private SyntaxNode GetUpdatedTypeDeclaration(TypeDeclarationSyntax node) - { - var newMembers = VisitList(ReplaceMembers(node.Members, global: false)); - return node.WithMembers(newMembers); + private SyntaxNode GetUpdatedTypeDeclaration(TypeDeclarationSyntax node) + { + var newMembers = VisitList(ReplaceMembers(node.Members, global: false)); + return node.WithMembers(newMembers); + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs index fc1648c02aea7..c7773872f9371 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs @@ -17,167 +17,170 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private abstract partial class CSharpCodeGenerator + internal sealed partial class CSharpMethodExtractor { - private sealed class ExpressionCodeGenerator( - CSharpSelectionResult selectionResult, - AnalyzerResult analyzerResult, - ExtractMethodGenerationOptions options, - bool localFunction) : CSharpCodeGenerator(selectionResult, analyzerResult, options, localFunction) + private abstract partial class CSharpCodeGenerator { - protected override SyntaxToken CreateMethodName() + private sealed class ExpressionCodeGenerator( + CSharpSelectionResult selectionResult, + AnalyzerResult analyzerResult, + ExtractMethodGenerationOptions options, + bool localFunction) : CSharpCodeGenerator(selectionResult, analyzerResult, options, localFunction) { - var methodName = GenerateMethodNameFromUserPreference(); - - var containingScope = this.SelectionResult.GetContainingScope(); + protected override SyntaxToken CreateMethodName() + { + var methodName = GenerateMethodNameFromUserPreference(); - methodName = GetMethodNameBasedOnExpression(methodName, containingScope); + var containingScope = this.SelectionResult.GetContainingScope(); - var semanticModel = SemanticDocument.SemanticModel; - var nameGenerator = new UniqueNameGenerator(semanticModel); - return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(containingScope, methodName)); - } + methodName = GetMethodNameBasedOnExpression(methodName, containingScope); - private static string GetMethodNameBasedOnExpression(string methodName, SyntaxNode expression) - { - if (expression.Parent != null && - expression.Parent.Kind() == SyntaxKind.EqualsValueClause && - expression.Parent.Parent != null && - expression.Parent.Parent.Kind() == SyntaxKind.VariableDeclarator) - { - var name = ((VariableDeclaratorSyntax)expression.Parent.Parent).Identifier.ValueText; - return (name != null && name.Length > 0) ? MakeMethodName("Get", name, methodName.Equals(NewMethodCamelCaseStr)) : methodName; + var semanticModel = SemanticDocument.SemanticModel; + var nameGenerator = new UniqueNameGenerator(semanticModel); + return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(containingScope, methodName)); } - if (expression is MemberAccessExpressionSyntax memberAccess) + private static string GetMethodNameBasedOnExpression(string methodName, SyntaxNode expression) { - expression = memberAccess.Name; - } + if (expression.Parent != null && + expression.Parent.Kind() == SyntaxKind.EqualsValueClause && + expression.Parent.Parent != null && + expression.Parent.Parent.Kind() == SyntaxKind.VariableDeclarator) + { + var name = ((VariableDeclaratorSyntax)expression.Parent.Parent).Identifier.ValueText; + return (name != null && name.Length > 0) ? MakeMethodName("Get", name, methodName.Equals(NewMethodCamelCaseStr)) : methodName; + } - if (expression is NameSyntax) - { - SimpleNameSyntax unqualifiedName; + if (expression is MemberAccessExpressionSyntax memberAccess) + { + expression = memberAccess.Name; + } - switch (expression.Kind()) + if (expression is NameSyntax) { - case SyntaxKind.IdentifierName: - case SyntaxKind.GenericName: - unqualifiedName = (SimpleNameSyntax)expression; - break; - case SyntaxKind.QualifiedName: - unqualifiedName = ((QualifiedNameSyntax)expression).Right; - break; - case SyntaxKind.AliasQualifiedName: - unqualifiedName = ((AliasQualifiedNameSyntax)expression).Name; - break; - default: - throw new System.NotSupportedException("Unexpected name kind: " + expression.Kind().ToString()); + SimpleNameSyntax unqualifiedName; + + switch (expression.Kind()) + { + case SyntaxKind.IdentifierName: + case SyntaxKind.GenericName: + unqualifiedName = (SimpleNameSyntax)expression; + break; + case SyntaxKind.QualifiedName: + unqualifiedName = ((QualifiedNameSyntax)expression).Right; + break; + case SyntaxKind.AliasQualifiedName: + unqualifiedName = ((AliasQualifiedNameSyntax)expression).Name; + break; + default: + throw new System.NotSupportedException("Unexpected name kind: " + expression.Kind().ToString()); + } + + var unqualifiedNameIdentifierValueText = unqualifiedName.Identifier.ValueText; + return (unqualifiedNameIdentifierValueText != null && unqualifiedNameIdentifierValueText.Length > 0) ? + MakeMethodName("Get", unqualifiedNameIdentifierValueText, methodName.Equals(NewMethodCamelCaseStr)) : methodName; } - var unqualifiedNameIdentifierValueText = unqualifiedName.Identifier.ValueText; - return (unqualifiedNameIdentifierValueText != null && unqualifiedNameIdentifierValueText.Length > 0) ? - MakeMethodName("Get", unqualifiedNameIdentifierValueText, methodName.Equals(NewMethodCamelCaseStr)) : methodName; + return methodName; } - return methodName; - } - - protected override ImmutableArray GetInitialStatementsForMethodDefinitions() - { - Contract.ThrowIfFalse(this.SelectionResult.SelectionInExpression); + protected override ImmutableArray GetInitialStatementsForMethodDefinitions() + { + Contract.ThrowIfFalse(this.SelectionResult.IsExtractMethodOnExpression); - // special case for array initializer - var returnType = AnalyzerResult.ReturnType; - var containingScope = this.SelectionResult.GetContainingScope(); + // special case for array initializer + var returnType = AnalyzerResult.ReturnType; + var containingScope = this.SelectionResult.GetContainingScope(); - ExpressionSyntax expression; - if (returnType.TypeKind == TypeKind.Array && containingScope is InitializerExpressionSyntax) - { - var typeSyntax = returnType.GenerateTypeSyntax(); + ExpressionSyntax expression; + if (returnType.TypeKind == TypeKind.Array && containingScope is InitializerExpressionSyntax) + { + var typeSyntax = returnType.GenerateTypeSyntax(); - expression = SyntaxFactory.ArrayCreationExpression(typeSyntax as ArrayTypeSyntax, containingScope as InitializerExpressionSyntax); - } - else - { - expression = containingScope as ExpressionSyntax; - } + expression = SyntaxFactory.ArrayCreationExpression(typeSyntax as ArrayTypeSyntax, containingScope as InitializerExpressionSyntax); + } + else + { + expression = containingScope as ExpressionSyntax; + } - if (AnalyzerResult.HasReturnType) - { - return [SyntaxFactory.ReturnStatement( + if (AnalyzerResult.HasReturnType) + { + return [SyntaxFactory.ReturnStatement( WrapInCheckedExpressionIfNeeded(expression))]; - } - else - { - return [SyntaxFactory.ExpressionStatement( + } + else + { + return [SyntaxFactory.ExpressionStatement( WrapInCheckedExpressionIfNeeded(expression))]; + } } - } - private ExpressionSyntax WrapInCheckedExpressionIfNeeded(ExpressionSyntax expression) - { - var kind = this.SelectionResult.UnderCheckedExpressionContext(); - if (kind == SyntaxKind.None) + private ExpressionSyntax WrapInCheckedExpressionIfNeeded(ExpressionSyntax expression) { - return expression; - } + var kind = this.SelectionResult.UnderCheckedExpressionContext(); + if (kind == SyntaxKind.None) + { + return expression; + } - return SyntaxFactory.CheckedExpression(kind, expression); - } + return SyntaxFactory.CheckedExpression(kind, expression); + } - protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() - { - var scope = (SyntaxNode)this.SelectionResult.GetContainingScopeOf(); - scope ??= this.SelectionResult.GetContainingScopeOf(); + protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() + { + var scope = (SyntaxNode)this.SelectionResult.GetContainingScopeOf(); + scope ??= this.SelectionResult.GetContainingScopeOf(); - scope ??= this.SelectionResult.GetContainingScopeOf(); + scope ??= this.SelectionResult.GetContainingScopeOf(); - // This is similar to FieldDeclaration case but we only want to do this - // if the member has an expression body. - scope ??= this.SelectionResult.GetContainingScopeOf()?.Parent; + // This is similar to FieldDeclaration case but we only want to do this + // if the member has an expression body. + scope ??= this.SelectionResult.GetContainingScopeOf()?.Parent; - scope ??= this.SelectionResult.GetContainingScopeOf(); + scope ??= this.SelectionResult.GetContainingScopeOf(); - return scope; - } + return scope; + } - protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() - => GetFirstStatementOrInitializerSelectedAtCallSite(); + protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() + => GetFirstStatementOrInitializerSelectedAtCallSite(); - protected override async Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken) - { - var enclosingStatement = GetFirstStatementOrInitializerSelectedAtCallSite(); + protected override async Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken) + { + var enclosingStatement = GetFirstStatementOrInitializerSelectedAtCallSite(); - var callSignature = CreateCallSignature().WithAdditionalAnnotations(CallSiteAnnotation); + var callSignature = CreateCallSignature().WithAdditionalAnnotations(CallSiteAnnotation); - var sourceNode = this.SelectionResult.GetContainingScope(); - Contract.ThrowIfTrue( - sourceNode.Parent is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Name == sourceNode, - "invalid scope. given scope is not an expression"); + var sourceNode = this.SelectionResult.GetContainingScope(); + Contract.ThrowIfTrue( + sourceNode.Parent is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Name == sourceNode, + "invalid scope. given scope is not an expression"); - // To lower the chances that replacing sourceNode with callSignature will break the user's - // code, we make the enclosing statement semantically explicit. This ends up being a little - // bit more work because we need to annotate the sourceNode so that we can get back to it - // after rewriting the enclosing statement. - var updatedDocument = SemanticDocument.Document; - var sourceNodeAnnotation = new SyntaxAnnotation(); - var enclosingStatementAnnotation = new SyntaxAnnotation(); - var newEnclosingStatement = enclosingStatement - .ReplaceNode(sourceNode, sourceNode.WithAdditionalAnnotations(sourceNodeAnnotation)) - .WithAdditionalAnnotations(enclosingStatementAnnotation); + // To lower the chances that replacing sourceNode with callSignature will break the user's + // code, we make the enclosing statement semantically explicit. This ends up being a little + // bit more work because we need to annotate the sourceNode so that we can get back to it + // after rewriting the enclosing statement. + var updatedDocument = SemanticDocument.Document; + var sourceNodeAnnotation = new SyntaxAnnotation(); + var enclosingStatementAnnotation = new SyntaxAnnotation(); + var newEnclosingStatement = enclosingStatement + .ReplaceNode(sourceNode, sourceNode.WithAdditionalAnnotations(sourceNodeAnnotation)) + .WithAdditionalAnnotations(enclosingStatementAnnotation); - updatedDocument = await updatedDocument.ReplaceNodeAsync(enclosingStatement, newEnclosingStatement, cancellationToken).ConfigureAwait(false); + updatedDocument = await updatedDocument.ReplaceNodeAsync(enclosingStatement, newEnclosingStatement, cancellationToken).ConfigureAwait(false); - var updatedRoot = await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - newEnclosingStatement = updatedRoot.GetAnnotatedNodesAndTokens(enclosingStatementAnnotation).Single().AsNode(); + var updatedRoot = await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + newEnclosingStatement = updatedRoot.GetAnnotatedNodesAndTokens(enclosingStatementAnnotation).Single().AsNode(); - // because of the complexification we cannot guarantee that there is only one annotation. - // however complexification of names is prepended, so the last annotation should be the original one. - sourceNode = updatedRoot.GetAnnotatedNodesAndTokens(sourceNodeAnnotation).Last().AsNode(); + // because of the complexification we cannot guarantee that there is only one annotation. + // however complexification of names is prepended, so the last annotation should be the original one. + sourceNode = updatedRoot.GetAnnotatedNodesAndTokens(sourceNodeAnnotation).Last().AsNode(); - return newEnclosingStatement.ReplaceNode(sourceNode, callSignature); + return newEnclosingStatement.ReplaceNode(sourceNode, callSignature); + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs index f11ac324c8da4..5e7b63d2cbeaa 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs @@ -16,76 +16,79 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private abstract partial class CSharpCodeGenerator + internal sealed partial class CSharpMethodExtractor { - public sealed class MultipleStatementsCodeGenerator( - CSharpSelectionResult selectionResult, - AnalyzerResult analyzerResult, - ExtractMethodGenerationOptions options, - bool localFunction) : CSharpCodeGenerator(selectionResult, analyzerResult, options, localFunction) + private abstract partial class CSharpCodeGenerator { - protected override SyntaxToken CreateMethodName() - => GenerateMethodNameForStatementGenerators(); - - protected override ImmutableArray GetInitialStatementsForMethodDefinitions() + public sealed class MultipleStatementsCodeGenerator( + CSharpSelectionResult selectionResult, + AnalyzerResult analyzerResult, + ExtractMethodGenerationOptions options, + bool localFunction) : CSharpCodeGenerator(selectionResult, analyzerResult, options, localFunction) { - var firstSeen = false; - var firstStatementUnderContainer = this.SelectionResult.GetFirstStatementUnderContainer(); - var lastStatementUnderContainer = this.SelectionResult.GetLastStatementUnderContainer(); + protected override SyntaxToken CreateMethodName() + => GenerateMethodNameForStatementGenerators(); - using var _ = ArrayBuilder.GetInstance(out var list); - foreach (var statement in GetStatementsFromContainer(firstStatementUnderContainer.Parent)) + protected override ImmutableArray GetInitialStatementsForMethodDefinitions() { - // reset first seen - if (!firstSeen) - { - firstSeen = statement == firstStatementUnderContainer; - } + var firstSeen = false; + var firstStatementUnderContainer = this.SelectionResult.GetFirstStatementUnderContainer(); + var lastStatementUnderContainer = this.SelectionResult.GetLastStatementUnderContainer(); - // continue until we see the first statement - if (!firstSeen) + using var _ = ArrayBuilder.GetInstance(out var list); + foreach (var statement in GetStatementsFromContainer(firstStatementUnderContainer.Parent)) { - continue; - } + // reset first seen + if (!firstSeen) + { + firstSeen = statement == firstStatementUnderContainer; + } - list.Add(statement); + // continue until we see the first statement + if (!firstSeen) + { + continue; + } - // exit if we see last statement - if (statement == lastStatementUnderContainer) - { - break; - } - } + list.Add(statement); - return list.ToImmutableAndClear(); - } + // exit if we see last statement + if (statement == lastStatementUnderContainer) + { + break; + } + } - private static IEnumerable GetStatementsFromContainer(SyntaxNode node) - { - Contract.ThrowIfNull(node); - Contract.ThrowIfFalse(node.IsStatementContainerNode()); + return list.ToImmutableAndClear(); + } - return node switch + private static IEnumerable GetStatementsFromContainer(SyntaxNode node) { - BlockSyntax blockNode => blockNode.Statements, - SwitchSectionSyntax switchSectionNode => switchSectionNode.Statements, - GlobalStatementSyntax globalStatement => ((CompilationUnitSyntax)globalStatement.Parent).Members.OfType().Select(globalStatement => globalStatement.Statement), - _ => throw ExceptionUtilities.UnexpectedValue(node), - }; - } + Contract.ThrowIfNull(node); + Contract.ThrowIfFalse(node.IsStatementContainerNode()); - protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() - => this.SelectionResult.GetFirstStatementUnderContainer(); + return node switch + { + BlockSyntax blockNode => blockNode.Statements, + SwitchSectionSyntax switchSectionNode => switchSectionNode.Statements, + GlobalStatementSyntax globalStatement => ((CompilationUnitSyntax)globalStatement.Parent).Members.OfType().Select(globalStatement => globalStatement.Statement), + _ => throw ExceptionUtilities.UnexpectedValue(node), + }; + } - protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() - => this.SelectionResult.GetLastStatementUnderContainer(); + protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() + => this.SelectionResult.GetFirstStatementUnderContainer(); - protected override Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken) - { - var statement = GetStatementContainingInvocationToExtractedMethodWorker(); - return Task.FromResult(statement.WithAdditionalAnnotations(CallSiteAnnotation)); + protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() + => this.SelectionResult.GetLastStatementUnderContainer(); + + protected override Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken) + { + var statement = GetStatementContainingInvocationToExtractedMethodWorker(); + return Task.FromResult(statement.WithAdditionalAnnotations(CallSiteAnnotation)); + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.SingleStatementCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.SingleStatementCodeGenerator.cs index e7012e1c563f8..294da82bb5b2c 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.SingleStatementCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.SingleStatementCodeGenerator.cs @@ -13,39 +13,43 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private abstract partial class CSharpCodeGenerator + internal sealed partial class CSharpMethodExtractor { - public sealed class SingleStatementCodeGenerator( - CSharpSelectionResult selectionResult, - AnalyzerResult analyzerResult, - ExtractMethodGenerationOptions options, - bool localFunction) : CSharpCodeGenerator(selectionResult, analyzerResult, options, localFunction) + private abstract partial class CSharpCodeGenerator { - protected override SyntaxToken CreateMethodName() => GenerateMethodNameForStatementGenerators(); - - protected override ImmutableArray GetInitialStatementsForMethodDefinitions() - { - Contract.ThrowIfFalse(this.SelectionResult.IsExtractMethodOnSingleStatement()); - - return [this.SelectionResult.GetFirstStatement()]; - } - - protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() - => this.SelectionResult.GetFirstStatement(); - - protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() - { - // it is a single statement case. either first statement is same as last statement or - // last statement belongs (embedded statement) to the first statement. - return this.SelectionResult.GetFirstStatement(); - } - - protected override Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken) + public sealed class SingleStatementCodeGenerator( + CSharpSelectionResult selectionResult, + AnalyzerResult analyzerResult, + ExtractMethodGenerationOptions options, + bool localFunction) : CSharpCodeGenerator(selectionResult, analyzerResult, options, localFunction) { - var statement = GetStatementContainingInvocationToExtractedMethodWorker(); - return Task.FromResult(statement.WithAdditionalAnnotations(CallSiteAnnotation)); + protected override SyntaxToken CreateMethodName() + => GenerateMethodNameForStatementGenerators(); + + protected override ImmutableArray GetInitialStatementsForMethodDefinitions() + { + Contract.ThrowIfFalse(this.SelectionResult.IsExtractMethodOnSingleStatement); + + return [this.SelectionResult.GetFirstStatement()]; + } + + protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() + => this.SelectionResult.GetFirstStatement(); + + protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() + { + // it is a single statement case. either first statement is same as last statement or + // last statement belongs (embedded statement) to the first statement. + return this.SelectionResult.GetFirstStatement(); + } + + protected override Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken) + { + var statement = GetStatementContainingInvocationToExtractedMethodWorker(); + return Task.FromResult(statement.WithAdditionalAnnotations(CallSiteAnnotation)); + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs index 689acb438edd3..07872083295e8 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs @@ -33,836 +33,833 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; using static CSharpSyntaxTokens; using static SyntaxFactory; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private abstract partial class CSharpCodeGenerator : CodeGenerator + internal sealed partial class CSharpMethodExtractor { - private readonly SyntaxToken _methodName; - - private const string NewMethodPascalCaseStr = "NewMethod"; - private const string NewMethodCamelCaseStr = "newMethod"; - - public static Task GenerateAsync( - InsertionPoint insertionPoint, - CSharpSelectionResult selectionResult, - AnalyzerResult analyzerResult, - ExtractMethodGenerationOptions options, - bool localFunction, - CancellationToken cancellationToken) + private abstract partial class CSharpCodeGenerator : CodeGenerator { - var codeGenerator = Create(selectionResult, analyzerResult, options, localFunction); - return codeGenerator.GenerateAsync(insertionPoint, cancellationToken); - } - - public static CSharpCodeGenerator Create( - CSharpSelectionResult selectionResult, - AnalyzerResult analyzerResult, - ExtractMethodGenerationOptions options, - bool localFunction) - { - if (selectionResult.SelectionInExpression) - return new ExpressionCodeGenerator(selectionResult, analyzerResult, options, localFunction); + private readonly SyntaxToken _methodName; - if (selectionResult.IsExtractMethodOnSingleStatement()) - return new SingleStatementCodeGenerator(selectionResult, analyzerResult, options, localFunction); + private const string NewMethodPascalCaseStr = "NewMethod"; + private const string NewMethodCamelCaseStr = "newMethod"; - if (selectionResult.IsExtractMethodOnMultipleStatements()) - return new MultipleStatementsCodeGenerator(selectionResult, analyzerResult, options, localFunction); - - throw ExceptionUtilities.UnexpectedValue(selectionResult); - } + public static Task GenerateAsync( + InsertionPoint insertionPoint, + CSharpSelectionResult selectionResult, + AnalyzerResult analyzerResult, + ExtractMethodGenerationOptions options, + bool localFunction, + CancellationToken cancellationToken) + { + var codeGenerator = Create(selectionResult, analyzerResult, options, localFunction); + return codeGenerator.GenerateAsync(insertionPoint, cancellationToken); + } - protected CSharpCodeGenerator( - CSharpSelectionResult selectionResult, - AnalyzerResult analyzerResult, - ExtractMethodGenerationOptions options, - bool localFunction) - : base(selectionResult, analyzerResult, options, localFunction) - { - Contract.ThrowIfFalse(SemanticDocument == selectionResult.SemanticDocument); + public static CSharpCodeGenerator Create( + CSharpSelectionResult selectionResult, + AnalyzerResult analyzerResult, + ExtractMethodGenerationOptions options, + bool localFunction) + { + return selectionResult.SelectionType switch + { + SelectionType.Expression => new ExpressionCodeGenerator(selectionResult, analyzerResult, options, localFunction), + SelectionType.SingleStatement => new SingleStatementCodeGenerator(selectionResult, analyzerResult, options, localFunction), + SelectionType.MultipleStatements => new MultipleStatementsCodeGenerator(selectionResult, analyzerResult, options, localFunction), + var v => throw ExceptionUtilities.UnexpectedValue(v), + }; + } - var nameToken = CreateMethodName(); - _methodName = nameToken.WithAdditionalAnnotations(MethodNameAnnotation); - } + protected CSharpCodeGenerator( + CSharpSelectionResult selectionResult, + AnalyzerResult analyzerResult, + ExtractMethodGenerationOptions options, + bool localFunction) + : base(selectionResult, analyzerResult, options, localFunction) + { + Contract.ThrowIfFalse(SemanticDocument == selectionResult.SemanticDocument); - public override OperationStatus> GetNewMethodStatements(SyntaxNode insertionPointNode, CancellationToken cancellationToken) - { - var statements = CreateMethodBody(insertionPointNode, cancellationToken); - var status = CheckActiveStatements(statements); - return status.With(statements.CastArray()); - } + var nameToken = CreateMethodName(); + _methodName = nameToken.WithAdditionalAnnotations(MethodNameAnnotation); + } - protected override IMethodSymbol GenerateMethodDefinition( - SyntaxNode insertionPointNode, CancellationToken cancellationToken) - { - var statements = CreateMethodBody(insertionPointNode, cancellationToken); - statements = WrapInCheckStatementIfNeeded(statements); - - var methodSymbol = CodeGenerationSymbolFactory.CreateMethodSymbol( - attributes: [], - accessibility: Accessibility.Private, - modifiers: CreateMethodModifiers(), - returnType: AnalyzerResult.ReturnType, - refKind: AnalyzerResult.ReturnsByRef ? RefKind.Ref : RefKind.None, - explicitInterfaceImplementations: default, - name: _methodName.ToString(), - typeParameters: CreateMethodTypeParameters(), - parameters: CreateMethodParameters(), - statements: statements.CastArray(), - methodKind: this.LocalFunction ? MethodKind.LocalFunction : MethodKind.Ordinary); - - return MethodDefinitionAnnotation.AddAnnotationToSymbol( - Formatter.Annotation.AddAnnotationToSymbol(methodSymbol)); - } + public override OperationStatus> GetNewMethodStatements(SyntaxNode insertionPointNode, CancellationToken cancellationToken) + { + var statements = CreateMethodBody(insertionPointNode, cancellationToken); + var status = CheckActiveStatements(statements); + return status.With(statements.CastArray()); + } - protected override async Task GenerateBodyForCallSiteContainerAsync( - SyntaxNode insertionPointNode, - SyntaxNode container, - CancellationToken cancellationToken) - { - var variableMapToRemove = CreateVariableDeclarationToRemoveMap( - AnalyzerResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken), cancellationToken); - var firstStatementToRemove = GetFirstStatementOrInitializerSelectedAtCallSite(); - var lastStatementToRemove = GetLastStatementOrInitializerSelectedAtCallSite(); + protected override IMethodSymbol GenerateMethodDefinition( + SyntaxNode insertionPointNode, CancellationToken cancellationToken) + { + var statements = CreateMethodBody(insertionPointNode, cancellationToken); + statements = WrapInCheckStatementIfNeeded(statements); + + var methodSymbol = CodeGenerationSymbolFactory.CreateMethodSymbol( + attributes: [], + accessibility: Accessibility.Private, + modifiers: CreateMethodModifiers(), + returnType: AnalyzerResult.ReturnType, + refKind: AnalyzerResult.ReturnsByRef ? RefKind.Ref : RefKind.None, + explicitInterfaceImplementations: default, + name: _methodName.ToString(), + typeParameters: CreateMethodTypeParameters(), + parameters: CreateMethodParameters(), + statements: statements.CastArray(), + methodKind: this.LocalFunction ? MethodKind.LocalFunction : MethodKind.Ordinary); + + return MethodDefinitionAnnotation.AddAnnotationToSymbol( + Formatter.Annotation.AddAnnotationToSymbol(methodSymbol)); + } - Contract.ThrowIfFalse(firstStatementToRemove.Parent == lastStatementToRemove.Parent - || CSharpSyntaxFacts.Instance.AreStatementsInSameContainer(firstStatementToRemove, lastStatementToRemove)); + protected override async Task GenerateBodyForCallSiteContainerAsync( + SyntaxNode insertionPointNode, + SyntaxNode container, + CancellationToken cancellationToken) + { + var variableMapToRemove = CreateVariableDeclarationToRemoveMap( + AnalyzerResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken), cancellationToken); + var firstStatementToRemove = GetFirstStatementOrInitializerSelectedAtCallSite(); + var lastStatementToRemove = GetLastStatementOrInitializerSelectedAtCallSite(); + + var statementsToInsert = await CreateStatementsOrInitializerToInsertAtCallSiteAsync( + insertionPointNode, cancellationToken).ConfigureAwait(false); + + var callSiteGenerator = new CallSiteContainerRewriter( + container, + variableMapToRemove, + firstStatementToRemove, + lastStatementToRemove, + statementsToInsert); + + return container.CopyAnnotationsTo(callSiteGenerator.Generate()).WithAdditionalAnnotations(Formatter.Annotation); + } - var statementsToInsert = await CreateStatementsOrInitializerToInsertAtCallSiteAsync( - insertionPointNode, cancellationToken).ConfigureAwait(false); + private async Task> CreateStatementsOrInitializerToInsertAtCallSiteAsync( + SyntaxNode insertionPointNode, CancellationToken cancellationToken) + { + var selectedNode = GetFirstStatementOrInitializerSelectedAtCallSite(); - var callSiteGenerator = new CallSiteContainerRewriter( - container, - variableMapToRemove, - firstStatementToRemove, - lastStatementToRemove, - statementsToInsert); + // field initializer, constructor initializer, expression bodied member case + if (selectedNode is ConstructorInitializerSyntax or FieldDeclarationSyntax or PrimaryConstructorBaseTypeSyntax || + IsExpressionBodiedMember(selectedNode) || + IsExpressionBodiedAccessor(selectedNode)) + { + var statement = await GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken).ConfigureAwait(false); + return [statement]; + } - return container.CopyAnnotationsTo(callSiteGenerator.Generate()).WithAdditionalAnnotations(Formatter.Annotation); - } + // regular case + var semanticModel = SemanticDocument.SemanticModel; + var postProcessor = new PostProcessor(semanticModel, insertionPointNode.SpanStart); - private async Task> CreateStatementsOrInitializerToInsertAtCallSiteAsync( - SyntaxNode insertionPointNode, CancellationToken cancellationToken) - { - var selectedNode = GetFirstStatementOrInitializerSelectedAtCallSite(); + var statements = AddSplitOrMoveDeclarationOutStatementsToCallSite(cancellationToken); + statements = postProcessor.MergeDeclarationStatements(statements); + statements = AddAssignmentStatementToCallSite(statements, cancellationToken); + statements = await AddInvocationAtCallSiteAsync(statements, cancellationToken).ConfigureAwait(false); + statements = AddReturnIfUnreachable(statements); - // field initializer, constructor initializer, expression bodied member case - if (selectedNode is ConstructorInitializerSyntax or FieldDeclarationSyntax or PrimaryConstructorBaseTypeSyntax || - IsExpressionBodiedMember(selectedNode) || - IsExpressionBodiedAccessor(selectedNode)) - { - var statement = await GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken).ConfigureAwait(false); - return [statement]; + return statements.CastArray(); } - // regular case - var semanticModel = SemanticDocument.SemanticModel; - var postProcessor = new PostProcessor(semanticModel, insertionPointNode.SpanStart); + protected override bool ShouldLocalFunctionCaptureParameter(SyntaxNode node) + => node.SyntaxTree.Options.LanguageVersion() < LanguageVersion.CSharp8; - var statements = AddSplitOrMoveDeclarationOutStatementsToCallSite(cancellationToken); - statements = postProcessor.MergeDeclarationStatements(statements); - statements = AddAssignmentStatementToCallSite(statements, cancellationToken); - statements = await AddInvocationAtCallSiteAsync(statements, cancellationToken).ConfigureAwait(false); - statements = AddReturnIfUnreachable(statements); - - return statements.CastArray(); - } + private static bool IsExpressionBodiedMember(SyntaxNode node) + => node is MemberDeclarationSyntax member && member.GetExpressionBody() != null; - protected override bool ShouldLocalFunctionCaptureParameter(SyntaxNode node) - => node.SyntaxTree.Options.LanguageVersion() < LanguageVersion.CSharp8; + private static bool IsExpressionBodiedAccessor(SyntaxNode node) + => node is AccessorDeclarationSyntax accessor && accessor.ExpressionBody != null; - private static bool IsExpressionBodiedMember(SyntaxNode node) - => node is MemberDeclarationSyntax member && member.GetExpressionBody() != null; + private SimpleNameSyntax CreateMethodNameForInvocation() + { + return AnalyzerResult.MethodTypeParametersInDeclaration.IsEmpty + ? IdentifierName(_methodName) + : GenericName(_methodName, TypeArgumentList(CreateMethodCallTypeVariables())); + } - private static bool IsExpressionBodiedAccessor(SyntaxNode node) - => node is AccessorDeclarationSyntax accessor && accessor.ExpressionBody != null; + private SeparatedSyntaxList CreateMethodCallTypeVariables() + { + Contract.ThrowIfTrue(AnalyzerResult.MethodTypeParametersInDeclaration.IsEmpty); - private SimpleNameSyntax CreateMethodNameForInvocation() - { - return AnalyzerResult.MethodTypeParametersInDeclaration.IsEmpty - ? IdentifierName(_methodName) - : GenericName(_methodName, TypeArgumentList(CreateMethodCallTypeVariables())); - } + // propagate any type variable used in extracted code + return [.. AnalyzerResult.MethodTypeParametersInDeclaration.Select(m => SyntaxFactory.ParseTypeName(m.Name))]; + } - private SeparatedSyntaxList CreateMethodCallTypeVariables() - { - Contract.ThrowIfTrue(AnalyzerResult.MethodTypeParametersInDeclaration.IsEmpty); + protected override SyntaxNode GetCallSiteContainerFromOutermostMoveInVariable(CancellationToken cancellationToken) + { + var outmostVariable = GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken); + if (outmostVariable == null) + return null; - // propagate any type variable used in extracted code - return [.. AnalyzerResult.MethodTypeParametersInDeclaration.Select(m => SyntaxFactory.ParseTypeName(m.Name))]; - } + var idToken = outmostVariable.GetIdentifierTokenAtDeclaration(SemanticDocument); + var declStatement = idToken.GetAncestor(); + Contract.ThrowIfNull(declStatement); + Contract.ThrowIfFalse(declStatement.Parent.IsStatementContainerNode()); - protected override SyntaxNode GetCallSiteContainerFromOutermostMoveInVariable(CancellationToken cancellationToken) - { - var outmostVariable = GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken); - if (outmostVariable == null) - return null; + return declStatement.Parent; + } - var idToken = outmostVariable.GetIdentifierTokenAtDeclaration(SemanticDocument); - var declStatement = idToken.GetAncestor(); - Contract.ThrowIfNull(declStatement); - Contract.ThrowIfFalse(declStatement.Parent.IsStatementContainerNode()); + private DeclarationModifiers CreateMethodModifiers() + { + var isUnsafe = this.SelectionResult.ShouldPutUnsafeModifier(); + var isAsync = this.SelectionResult.CreateAsyncMethod(); + var isStatic = !AnalyzerResult.UseInstanceMember; + var isReadOnly = AnalyzerResult.ShouldBeReadOnly; - return declStatement.Parent; - } + // Static local functions are only supported in C# 8.0 and later + var languageVersion = SemanticDocument.SyntaxTree.Options.LanguageVersion(); - private DeclarationModifiers CreateMethodModifiers() - { - var isUnsafe = this.SelectionResult.ShouldPutUnsafeModifier(); - var isAsync = this.SelectionResult.CreateAsyncMethod(); - var isStatic = !AnalyzerResult.UseInstanceMember; - var isReadOnly = AnalyzerResult.ShouldBeReadOnly; + if (LocalFunction && (!Options.PreferStaticLocalFunction.Value || languageVersion < LanguageVersion.CSharp8)) + { + isStatic = false; + } - // Static local functions are only supported in C# 8.0 and later - var languageVersion = SemanticDocument.SyntaxTree.Options.LanguageVersion(); + // UseInstanceMember will be false for interface members, but extracting a non-static + // member to a static member has a very different meaning for interfaces so we need + // an extra check here. + if (!LocalFunction && IsNonStaticInterfaceMember()) + { + isStatic = false; + } - if (LocalFunction && (!Options.PreferStaticLocalFunction.Value || languageVersion < LanguageVersion.CSharp8)) - { - isStatic = false; + return new DeclarationModifiers( + isUnsafe: isUnsafe, + isAsync: isAsync, + isStatic: isStatic, + isReadOnly: isReadOnly); } - // UseInstanceMember will be false for interface members, but extracting a non-static - // member to a static member has a very different meaning for interfaces so we need - // an extra check here. - if (!LocalFunction && IsNonStaticInterfaceMember()) + private bool IsNonStaticInterfaceMember() { - isStatic = false; - } + var typeDecl = SelectionResult.GetContainingScopeOf(); + if (typeDecl is null) + return false; - return new DeclarationModifiers( - isUnsafe: isUnsafe, - isAsync: isAsync, - isStatic: isStatic, - isReadOnly: isReadOnly); - } + if (!typeDecl.IsKind(SyntaxKind.InterfaceDeclaration)) + return false; - private bool IsNonStaticInterfaceMember() - { - var typeDecl = SelectionResult.GetContainingScopeOf(); - if (typeDecl is null) - return false; - - if (!typeDecl.IsKind(SyntaxKind.InterfaceDeclaration)) - return false; - - var memberDecl = SelectionResult.GetContainingScopeOf(); - if (memberDecl is null) - return false; - - return !memberDecl.Modifiers.Any(SyntaxKind.StaticKeyword); - } + var memberDecl = SelectionResult.GetContainingScopeOf(); + if (memberDecl is null) + return false; - private static SyntaxKind GetParameterRefSyntaxKind(ParameterBehavior parameterBehavior) - { - return parameterBehavior == ParameterBehavior.Ref - ? SyntaxKind.RefKeyword - : parameterBehavior == ParameterBehavior.Out ? - SyntaxKind.OutKeyword : SyntaxKind.None; - } + return !memberDecl.Modifiers.Any(SyntaxKind.StaticKeyword); + } - private ImmutableArray CreateMethodBody( - SyntaxNode insertionPoint, CancellationToken cancellationToken) - { - var statements = GetInitialStatementsForMethodDefinitions(); + private static SyntaxKind GetParameterRefSyntaxKind(ParameterBehavior parameterBehavior) + { + return parameterBehavior == ParameterBehavior.Ref + ? SyntaxKind.RefKeyword + : parameterBehavior == ParameterBehavior.Out ? + SyntaxKind.OutKeyword : SyntaxKind.None; + } - statements = SplitOrMoveDeclarationIntoMethodDefinition(insertionPoint, statements, cancellationToken); - statements = MoveDeclarationOutFromMethodDefinition(statements, cancellationToken); - statements = AppendReturnStatementIfNeeded(statements); - statements = CleanupCode(statements); + private ImmutableArray CreateMethodBody( + SyntaxNode insertionPoint, CancellationToken cancellationToken) + { + var statements = GetInitialStatementsForMethodDefinitions(); - return statements; - } + statements = SplitOrMoveDeclarationIntoMethodDefinition(insertionPoint, statements, cancellationToken); + statements = MoveDeclarationOutFromMethodDefinition(statements, cancellationToken); + statements = AppendReturnStatementIfNeeded(statements); + statements = CleanupCode(statements); - private ImmutableArray WrapInCheckStatementIfNeeded(ImmutableArray statements) - { - var kind = this.SelectionResult.UnderCheckedStatementContext(); - if (kind == SyntaxKind.None) return statements; + } - return statements is [BlockSyntax block] - ? [CheckedStatement(kind, block)] - : [CheckedStatement(kind, Block(statements))]; - } - - private static ImmutableArray CleanupCode(ImmutableArray statements) - { - statements = PostProcessor.RemoveRedundantBlock(statements); - statements = PostProcessor.RemoveDeclarationAssignmentPattern(statements); - statements = PostProcessor.RemoveInitializedDeclarationAndReturnPattern(statements); + private ImmutableArray WrapInCheckStatementIfNeeded(ImmutableArray statements) + { + var kind = this.SelectionResult.UnderCheckedStatementContext(); + if (kind == SyntaxKind.None) + return statements; - return statements; - } + return statements is [BlockSyntax block] + ? [CheckedStatement(kind, block)] + : [CheckedStatement(kind, Block(statements))]; + } - private static OperationStatus CheckActiveStatements(ImmutableArray statements) - { - if (statements.IsEmpty) - return OperationStatus.NoActiveStatement; + private static ImmutableArray CleanupCode(ImmutableArray statements) + { + statements = PostProcessor.RemoveRedundantBlock(statements); + statements = PostProcessor.RemoveDeclarationAssignmentPattern(statements); + statements = PostProcessor.RemoveInitializedDeclarationAndReturnPattern(statements); - if (statements is [ReturnStatementSyntax { Expression: null }]) - return OperationStatus.NoActiveStatement; + return statements; + } - // Look for at least one non local-variable-decl statement, or at least one local variable with an initializer. - foreach (var statement in statements) + private static OperationStatus CheckActiveStatements(ImmutableArray statements) { - if (statement is not LocalDeclarationStatementSyntax declStatement) - return OperationStatus.SucceededStatus; + if (statements.IsEmpty) + return OperationStatus.NoActiveStatement; + + if (statements is [ReturnStatementSyntax { Expression: null }]) + return OperationStatus.NoActiveStatement; - foreach (var variable in declStatement.Declaration.Variables) + // Look for at least one non local-variable-decl statement, or at least one local variable with an initializer. + foreach (var statement in statements) { - if (variable.Initializer != null) + if (statement is not LocalDeclarationStatementSyntax declStatement) return OperationStatus.SucceededStatus; + + foreach (var variable in declStatement.Declaration.Variables) + { + if (variable.Initializer != null) + return OperationStatus.SucceededStatus; + } } - } - return OperationStatus.NoActiveStatement; - } + return OperationStatus.NoActiveStatement; + } - private ImmutableArray MoveDeclarationOutFromMethodDefinition( - ImmutableArray statements, CancellationToken cancellationToken) - { - using var _1 = ArrayBuilder.GetInstance(out var result); + private ImmutableArray MoveDeclarationOutFromMethodDefinition( + ImmutableArray statements, CancellationToken cancellationToken) + { + using var _1 = ArrayBuilder.GetInstance(out var result); - var variableToRemoveMap = CreateVariableDeclarationToRemoveMap( - AnalyzerResult.GetVariablesToMoveOutToCallSiteOrDelete(cancellationToken), cancellationToken); + var variableToRemoveMap = CreateVariableDeclarationToRemoveMap( + AnalyzerResult.GetVariablesToMoveOutToCallSiteOrDelete(cancellationToken), cancellationToken); - statements = statements.SelectAsArray(s => FixDeclarationExpressionsAndDeclarationPatterns(s, variableToRemoveMap)); + statements = statements.SelectAsArray(s => FixDeclarationExpressionsAndDeclarationPatterns(s, variableToRemoveMap)); - foreach (var statement in statements) - { - if (statement is not LocalDeclarationStatementSyntax declarationStatement || declarationStatement.Declaration.Variables.FullSpan.IsEmpty) + foreach (var statement in statements) { - // if given statement is not decl statement. - result.Add(statement); - continue; - } + if (statement is not LocalDeclarationStatementSyntax declarationStatement || declarationStatement.Declaration.Variables.FullSpan.IsEmpty) + { + // if given statement is not decl statement. + result.Add(statement); + continue; + } - using var _2 = ArrayBuilder.GetInstance(out var expressionStatements); - using var _3 = ArrayBuilder.GetInstance(out var variables); - using var _4 = ArrayBuilder.GetInstance(out var triviaList); + using var _2 = ArrayBuilder.GetInstance(out var expressionStatements); + using var _3 = ArrayBuilder.GetInstance(out var variables); + using var _4 = ArrayBuilder.GetInstance(out var triviaList); - // When we modify the declaration to an initialization we have to preserve the leading trivia - var firstVariableToAttachTrivia = true; + // When we modify the declaration to an initialization we have to preserve the leading trivia + var firstVariableToAttachTrivia = true; - var isUsingDeclarationAsReturnValue = this.AnalyzerResult.VariablesToUseAsReturnValue is [var variable] && - variable.GetOriginalIdentifierToken(cancellationToken) != default && - variable.GetIdentifierTokenAtDeclaration(declarationStatement) != default; + var isUsingDeclarationAsReturnValue = this.AnalyzerResult.VariablesToUseAsReturnValue is [var variable] && + variable.GetOriginalIdentifierToken(cancellationToken) != default && + variable.GetIdentifierTokenAtDeclaration(declarationStatement) != default; - // go through each var decls in decl statement, and create new assignment if - // variable is initialized at decl. - foreach (var variableDeclaration in declarationStatement.Declaration.Variables) - { - if (variableToRemoveMap.HasSyntaxAnnotation(variableDeclaration)) + // go through each var decls in decl statement, and create new assignment if + // variable is initialized at decl. + foreach (var variableDeclaration in declarationStatement.Declaration.Variables) { - if (variableDeclaration.Initializer != null) + if (variableToRemoveMap.HasSyntaxAnnotation(variableDeclaration)) { - var identifier = ApplyTriviaFromDeclarationToAssignmentIdentifier(declarationStatement, firstVariableToAttachTrivia, variableDeclaration); + if (variableDeclaration.Initializer != null) + { + var identifier = ApplyTriviaFromDeclarationToAssignmentIdentifier(declarationStatement, firstVariableToAttachTrivia, variableDeclaration); + + // move comments with the variable here + expressionStatements.Add(ExpressionStatement(AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, IdentifierName(identifier), variableDeclaration.Initializer.Value))); + } + else + { + // we don't remove trivia around tokens we remove + triviaList.AddRange(variableDeclaration.GetLeadingTrivia()); + triviaList.AddRange(variableDeclaration.GetTrailingTrivia()); + } - // move comments with the variable here - expressionStatements.Add(ExpressionStatement(AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, IdentifierName(identifier), variableDeclaration.Initializer.Value))); + firstVariableToAttachTrivia = false; + continue; } - else + + // Prepend the trivia from the declarations without initialization to the next persisting variable declaration + if (triviaList.Count > 0) { - // we don't remove trivia around tokens we remove - triviaList.AddRange(variableDeclaration.GetLeadingTrivia()); - triviaList.AddRange(variableDeclaration.GetTrailingTrivia()); + variables.Add(variableDeclaration.WithPrependedLeadingTrivia(triviaList)); + triviaList.Clear(); + firstVariableToAttachTrivia = false; + continue; } firstVariableToAttachTrivia = false; - continue; + variables.Add(variableDeclaration); } - // Prepend the trivia from the declarations without initialization to the next persisting variable declaration - if (triviaList.Count > 0) + if (variables.Count == 0 && triviaList.Count > 0) { - variables.Add(variableDeclaration.WithPrependedLeadingTrivia(triviaList)); + // well, there are trivia associated with the node. + // we can't just delete the node since then, we will lose + // the trivia. unfortunately, it is not easy to attach the trivia + // to next token. for now, create an empty statement and associate the + // trivia to the statement + + // TODO : think about a way to trivia attached to next token + result.Add(EmptyStatement(Token([.. triviaList], SyntaxKind.SemicolonToken, [ElasticMarker]))); triviaList.Clear(); - firstVariableToAttachTrivia = false; - continue; } - firstVariableToAttachTrivia = false; - variables.Add(variableDeclaration); - } - - if (variables.Count == 0 && triviaList.Count > 0) - { - // well, there are trivia associated with the node. - // we can't just delete the node since then, we will lose - // the trivia. unfortunately, it is not easy to attach the trivia - // to next token. for now, create an empty statement and associate the - // trivia to the statement - - // TODO : think about a way to trivia attached to next token - result.Add(EmptyStatement(Token([.. triviaList], SyntaxKind.SemicolonToken, [ElasticMarker]))); - triviaList.Clear(); - } + // return survived var decls + if (variables.Count > 0) + { + result.Add(LocalDeclarationStatement( + isUsingDeclarationAsReturnValue ? default : declarationStatement.AwaitKeyword, + isUsingDeclarationAsReturnValue ? default : declarationStatement.UsingKeyword, + declarationStatement.Modifiers, + VariableDeclaration( + declarationStatement.Declaration.Type, + [.. variables]), + declarationStatement.SemicolonToken.WithPrependedLeadingTrivia(triviaList))); + triviaList.Clear(); + } - // return survived var decls - if (variables.Count > 0) - { - result.Add(LocalDeclarationStatement( - isUsingDeclarationAsReturnValue ? default : declarationStatement.AwaitKeyword, - isUsingDeclarationAsReturnValue ? default : declarationStatement.UsingKeyword, - declarationStatement.Modifiers, - VariableDeclaration( - declarationStatement.Declaration.Type, - [.. variables]), - declarationStatement.SemicolonToken.WithPrependedLeadingTrivia(triviaList))); - triviaList.Clear(); + // return any expression statement if there was any + result.AddRange(expressionStatements); } - // return any expression statement if there was any - result.AddRange(expressionStatements); + return result.ToImmutableAndClear(); } - return result.ToImmutableAndClear(); - } - - /// - /// If the statement has an out var declaration expression for a variable which - /// needs to be removed, we need to turn it into a plain out parameter, so that - /// it doesn't declare a duplicate variable. - /// If the statement has a pattern declaration (such as 3 is int i) for a variable - /// which needs to be removed, we will annotate it as a conflict, since we don't have - /// a better refactoring. - /// - private static StatementSyntax FixDeclarationExpressionsAndDeclarationPatterns(StatementSyntax statement, - HashSet variablesToRemove) - { - var replacements = new Dictionary(); + /// + /// If the statement has an out var declaration expression for a variable which + /// needs to be removed, we need to turn it into a plain out parameter, so that + /// it doesn't declare a duplicate variable. + /// If the statement has a pattern declaration (such as 3 is int i) for a variable + /// which needs to be removed, we will annotate it as a conflict, since we don't have + /// a better refactoring. + /// + private static StatementSyntax FixDeclarationExpressionsAndDeclarationPatterns(StatementSyntax statement, + HashSet variablesToRemove) + { + var replacements = new Dictionary(); - var declarations = statement.DescendantNodes() - .Where(n => n.Kind() is SyntaxKind.DeclarationExpression or SyntaxKind.DeclarationPattern); + var declarations = statement.DescendantNodes() + .Where(n => n.Kind() is SyntaxKind.DeclarationExpression or SyntaxKind.DeclarationPattern); - foreach (var node in declarations) - { - switch (node.Kind()) + foreach (var node in declarations) { - case SyntaxKind.DeclarationExpression: - { - var declaration = (DeclarationExpressionSyntax)node; - if (declaration.Designation.Kind() != SyntaxKind.SingleVariableDesignation) - { - break; - } - - var designation = (SingleVariableDesignationSyntax)declaration.Designation; - var name = designation.Identifier.ValueText; - if (variablesToRemove.HasSyntaxAnnotation(designation)) + switch (node.Kind()) + { + case SyntaxKind.DeclarationExpression: { - var newLeadingTrivia = new SyntaxTriviaList(); - newLeadingTrivia = newLeadingTrivia.AddRange(declaration.Type.GetLeadingTrivia()); - newLeadingTrivia = newLeadingTrivia.AddRange(declaration.Type.GetTrailingTrivia()); - newLeadingTrivia = newLeadingTrivia.AddRange(designation.GetLeadingTrivia()); - - replacements.Add(declaration, IdentifierName(designation.Identifier) - .WithLeadingTrivia(newLeadingTrivia)); - } + var declaration = (DeclarationExpressionSyntax)node; + if (declaration.Designation.Kind() != SyntaxKind.SingleVariableDesignation) + { + break; + } + + var designation = (SingleVariableDesignationSyntax)declaration.Designation; + var name = designation.Identifier.ValueText; + if (variablesToRemove.HasSyntaxAnnotation(designation)) + { + var newLeadingTrivia = new SyntaxTriviaList(); + newLeadingTrivia = newLeadingTrivia.AddRange(declaration.Type.GetLeadingTrivia()); + newLeadingTrivia = newLeadingTrivia.AddRange(declaration.Type.GetTrailingTrivia()); + newLeadingTrivia = newLeadingTrivia.AddRange(designation.GetLeadingTrivia()); + + replacements.Add(declaration, IdentifierName(designation.Identifier) + .WithLeadingTrivia(newLeadingTrivia)); + } - break; - } - - case SyntaxKind.DeclarationPattern: - { - var pattern = (DeclarationPatternSyntax)node; - if (!variablesToRemove.HasSyntaxAnnotation(pattern)) - { break; } - // We don't have a good refactoring for this, so we just annotate the conflict - // For instance, when a local declared by a pattern declaration (`3 is int i`) is - // used outside the block we're trying to extract. - if (pattern.Designation is not SingleVariableDesignationSyntax designation) + case SyntaxKind.DeclarationPattern: { + var pattern = (DeclarationPatternSyntax)node; + if (!variablesToRemove.HasSyntaxAnnotation(pattern)) + { + break; + } + + // We don't have a good refactoring for this, so we just annotate the conflict + // For instance, when a local declared by a pattern declaration (`3 is int i`) is + // used outside the block we're trying to extract. + if (pattern.Designation is not SingleVariableDesignationSyntax designation) + { + break; + } + + var identifier = designation.Identifier; + var annotation = ConflictAnnotation.Create(FeaturesResources.Conflict_s_detected); + var newIdentifier = identifier.WithAdditionalAnnotations(annotation); + var newDesignation = designation.WithIdentifier(newIdentifier); + replacements.Add(pattern, pattern.WithDesignation(newDesignation)); + break; } - - var identifier = designation.Identifier; - var annotation = ConflictAnnotation.Create(FeaturesResources.Conflict_s_detected); - var newIdentifier = identifier.WithAdditionalAnnotations(annotation); - var newDesignation = designation.WithIdentifier(newIdentifier); - replacements.Add(pattern, pattern.WithDesignation(newDesignation)); - - break; - } + } } - } - return statement.ReplaceNodes(replacements.Keys, (orig, partiallyReplaced) => replacements[orig]); - } + return statement.ReplaceNodes(replacements.Keys, (orig, partiallyReplaced) => replacements[orig]); + } - private static SyntaxToken ApplyTriviaFromDeclarationToAssignmentIdentifier(LocalDeclarationStatementSyntax declarationStatement, bool firstVariableToAttachTrivia, VariableDeclaratorSyntax variable) - { - var identifier = variable.Identifier; - var typeSyntax = declarationStatement.Declaration.Type; - if (firstVariableToAttachTrivia && typeSyntax != null) + private static SyntaxToken ApplyTriviaFromDeclarationToAssignmentIdentifier(LocalDeclarationStatementSyntax declarationStatement, bool firstVariableToAttachTrivia, VariableDeclaratorSyntax variable) { - var identifierLeadingTrivia = new SyntaxTriviaList(); - - if (typeSyntax.HasLeadingTrivia) + var identifier = variable.Identifier; + var typeSyntax = declarationStatement.Declaration.Type; + if (firstVariableToAttachTrivia && typeSyntax != null) { - identifierLeadingTrivia = identifierLeadingTrivia.AddRange(typeSyntax.GetLeadingTrivia()); - } + var identifierLeadingTrivia = new SyntaxTriviaList(); - identifierLeadingTrivia = identifierLeadingTrivia.AddRange(identifier.LeadingTrivia); - identifier = identifier.WithLeadingTrivia(identifierLeadingTrivia); - } + if (typeSyntax.HasLeadingTrivia) + { + identifierLeadingTrivia = identifierLeadingTrivia.AddRange(typeSyntax.GetLeadingTrivia()); + } - return identifier; - } + identifierLeadingTrivia = identifierLeadingTrivia.AddRange(identifier.LeadingTrivia); + identifier = identifier.WithLeadingTrivia(identifierLeadingTrivia); + } - private ImmutableArray SplitOrMoveDeclarationIntoMethodDefinition( - SyntaxNode insertionPointNode, - ImmutableArray statements, - CancellationToken cancellationToken) - { - var semanticModel = SemanticDocument.SemanticModel; - var postProcessor = new PostProcessor(semanticModel, insertionPointNode.SpanStart); + return identifier; + } - var declStatements = CreateDeclarationStatements(AnalyzerResult.GetVariablesToSplitOrMoveIntoMethodDefinition(cancellationToken), cancellationToken); - declStatements = postProcessor.MergeDeclarationStatements(declStatements); + private ImmutableArray SplitOrMoveDeclarationIntoMethodDefinition( + SyntaxNode insertionPointNode, + ImmutableArray statements, + CancellationToken cancellationToken) + { + var semanticModel = SemanticDocument.SemanticModel; + var postProcessor = new PostProcessor(semanticModel, insertionPointNode.SpanStart); - return declStatements.Concat(statements); - } + var declStatements = CreateDeclarationStatements(AnalyzerResult.GetVariablesToSplitOrMoveIntoMethodDefinition(cancellationToken), cancellationToken); + declStatements = postProcessor.MergeDeclarationStatements(declStatements); - protected override bool LastStatementOrHasReturnStatementInReturnableConstruct() - { - var lastStatement = GetLastStatementOrInitializerSelectedAtCallSite(); - var container = lastStatement.GetAncestorsOrThis().FirstOrDefault(n => n.IsReturnableConstruct()); - if (container == null) - { - // case such as field initializer - return false; + return declStatements.Concat(statements); } - var blockBody = container.GetBlockBody(); - if (blockBody == null) + protected override bool LastStatementOrHasReturnStatementInReturnableConstruct() { - // such as expression lambda. there is no statement - return false; - } + var lastStatement = GetLastStatementOrInitializerSelectedAtCallSite(); + var container = lastStatement.GetAncestorsOrThis().FirstOrDefault(n => n.IsReturnableConstruct()); + if (container == null) + { + // case such as field initializer + return false; + } - // check whether it is last statement except return statement - var statements = blockBody.Statements; - if (statements.Last() == lastStatement) - { - return true; - } + var blockBody = container.GetBlockBody(); + if (blockBody == null) + { + // such as expression lambda. there is no statement + return false; + } - var index = statements.IndexOf((StatementSyntax)lastStatement); - return statements[index + 1].Kind() == SyntaxKind.ReturnStatement; - } + // check whether it is last statement except return statement + var statements = blockBody.Statements; + if (statements.Last() == lastStatement) + { + return true; + } - protected override SyntaxToken CreateIdentifier(string name) - => name.ToIdentifierToken(); + var index = statements.IndexOf((StatementSyntax)lastStatement); + return statements[index + 1].Kind() == SyntaxKind.ReturnStatement; + } - protected override StatementSyntax CreateReturnStatement(string[] identifierNames) - => identifierNames.Length == 0 ? ReturnStatement() : - identifierNames.Length == 1 ? ReturnStatement(IdentifierName(identifierNames[0])) : - ReturnStatement(TupleExpression([.. identifierNames.Select(n => Argument(n.ToIdentifierName()))])); + protected override SyntaxToken CreateIdentifier(string name) + => name.ToIdentifierToken(); - protected override ExpressionSyntax CreateCallSignature() - { - var methodName = CreateMethodNameForInvocation().WithAdditionalAnnotations(Simplifier.Annotation); - ExpressionSyntax methodExpression = - this.AnalyzerResult.UseInstanceMember && this.ExtractMethodGenerationOptions.SimplifierOptions.QualifyMethodAccess.Value && !LocalFunction - ? MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), methodName) - : methodName; + protected override StatementSyntax CreateReturnStatement(string[] identifierNames) + => identifierNames.Length == 0 ? ReturnStatement() : + identifierNames.Length == 1 ? ReturnStatement(IdentifierName(identifierNames[0])) : + ReturnStatement(TupleExpression([.. identifierNames.Select(n => Argument(n.ToIdentifierName()))])); - var isLocalFunction = LocalFunction && ShouldLocalFunctionCaptureParameter(SemanticDocument.Root); + protected override ExpressionSyntax CreateCallSignature() + { + var methodName = CreateMethodNameForInvocation().WithAdditionalAnnotations(Simplifier.Annotation); + ExpressionSyntax methodExpression = + this.AnalyzerResult.UseInstanceMember && this.ExtractMethodGenerationOptions.SimplifierOptions.QualifyMethodAccess.Value && !LocalFunction + ? MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), methodName) + : methodName; - using var _ = ArrayBuilder.GetInstance(out var arguments); + var isLocalFunction = LocalFunction && ShouldLocalFunctionCaptureParameter(SemanticDocument.Root); - foreach (var argument in AnalyzerResult.MethodParameters) - { - if (!isLocalFunction || !argument.CanBeCapturedByLocalFunction) - { - var modifier = GetParameterRefSyntaxKind(argument.ParameterModifier); - var refOrOut = modifier == SyntaxKind.None ? default : Token(modifier); - arguments.Add(Argument(IdentifierName(argument.Name)).WithRefOrOutKeyword(refOrOut)); - } - } + using var _ = ArrayBuilder.GetInstance(out var arguments); - var invocation = (ExpressionSyntax)InvocationExpression(methodExpression, ArgumentList([.. arguments])); - if (this.SelectionResult.CreateAsyncMethod()) - { - if (this.SelectionResult.ShouldCallConfigureAwaitFalse()) + foreach (var argument in AnalyzerResult.MethodParameters) { - if (AnalyzerResult.ReturnType.GetMembers().Any(static x => x is IMethodSymbol - { - Name: nameof(Task.ConfigureAwait), - Parameters: [{ Type.SpecialType: SpecialType.System_Boolean }], - })) + if (!isLocalFunction || !argument.CanBeCapturedByLocalFunction) { - invocation = InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - invocation, - IdentifierName(nameof(Task.ConfigureAwait))), - ArgumentList([Argument(LiteralExpression(SyntaxKind.FalseLiteralExpression))])); + var modifier = GetParameterRefSyntaxKind(argument.ParameterModifier); + var refOrOut = modifier == SyntaxKind.None ? default : Token(modifier); + arguments.Add(Argument(IdentifierName(argument.Name)).WithRefOrOutKeyword(refOrOut)); } } - invocation = AwaitExpression(invocation); - } + var invocation = (ExpressionSyntax)InvocationExpression(methodExpression, ArgumentList([.. arguments])); + if (this.SelectionResult.CreateAsyncMethod()) + { + if (this.SelectionResult.ShouldCallConfigureAwaitFalse()) + { + if (AnalyzerResult.ReturnType.GetMembers().Any(static x => x is IMethodSymbol + { + Name: nameof(Task.ConfigureAwait), + Parameters: [{ Type.SpecialType: SpecialType.System_Boolean }], + })) + { + invocation = InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + invocation, + IdentifierName(nameof(Task.ConfigureAwait))), + ArgumentList([Argument(LiteralExpression(SyntaxKind.FalseLiteralExpression))])); + } + } - if (AnalyzerResult.ReturnsByRef) - invocation = RefExpression(invocation); + invocation = AwaitExpression(invocation); + } - return invocation; - } + if (AnalyzerResult.ReturnsByRef) + invocation = RefExpression(invocation); - protected override StatementSyntax CreateAssignmentExpressionStatement( - ImmutableArray variableInfos, ExpressionSyntax rvalue) - { - Contract.ThrowIfTrue(variableInfos.IsEmpty); - if (variableInfos is [var singleInfo]) - { - return ExpressionStatement(AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - singleInfo.Name.ToIdentifierName(), - rvalue)); + return invocation; } - else + + protected override StatementSyntax CreateAssignmentExpressionStatement( + ImmutableArray variableInfos, ExpressionSyntax rvalue) { - var tupleExpression = TupleExpression([.. variableInfos.Select(v => Argument(v.Name.ToIdentifierName()))]); - return ExpressionStatement(AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - tupleExpression, - rvalue)); + Contract.ThrowIfTrue(variableInfos.IsEmpty); + if (variableInfos is [var singleInfo]) + { + return ExpressionStatement(AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + singleInfo.Name.ToIdentifierName(), + rvalue)); + } + else + { + var tupleExpression = TupleExpression([.. variableInfos.Select(v => Argument(v.Name.ToIdentifierName()))]); + return ExpressionStatement(AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + tupleExpression, + rvalue)); + } } - } - protected override StatementSyntax CreateDeclarationStatement( - ImmutableArray variableInfos, - ExpressionSyntax initialValue, - CancellationToken cancellationToken) - { - Contract.ThrowIfTrue(variableInfos.Length == 0); - - if (variableInfos is [var singleVariable]) + protected override StatementSyntax CreateDeclarationStatement( + ImmutableArray variableInfos, + ExpressionSyntax initialValue, + CancellationToken cancellationToken) { - var type = singleVariable.GetVariableType(); - var typeNode = type.GenerateTypeSyntax(); + Contract.ThrowIfTrue(variableInfos.Length == 0); - var originalIdentifierToken = singleVariable.GetOriginalIdentifierToken(cancellationToken); + if (variableInfos is [var singleVariable]) + { + var type = singleVariable.GetVariableType(); + var typeNode = type.GenerateTypeSyntax(); - // Hierarchy being checked for to see if a using keyword is needed is - // Token -> VariableDeclarator -> VariableDeclaration -> LocalDeclaration - var usingKeyword = originalIdentifierToken.Parent?.Parent?.Parent is LocalDeclarationStatementSyntax { UsingKeyword.FullSpan.IsEmpty: false } - ? UsingKeyword - : default; + var originalIdentifierToken = singleVariable.GetOriginalIdentifierToken(cancellationToken); - var equalsValueClause = initialValue == null ? null : EqualsValueClause(value: initialValue); + // Hierarchy being checked for to see if a using keyword is needed is + // Token -> VariableDeclarator -> VariableDeclaration -> LocalDeclaration + var usingKeyword = originalIdentifierToken.Parent?.Parent?.Parent is LocalDeclarationStatementSyntax { UsingKeyword.FullSpan.IsEmpty: false } + ? UsingKeyword + : default; - return LocalDeclarationStatement( - VariableDeclaration(typeNode) - .AddVariables(VariableDeclarator(singleVariable.Name.ToIdentifierToken()) - .WithInitializer(equalsValueClause))) - .WithUsingKeyword(usingKeyword); - } - else - { - // Note, we do not use "Use var when apparent" here as no types are apparent when doing `... = - // NewMethod()`. If we have `use var elsewhere` we may try to generate `var (a, b, c)` if we're - // producing new variables for all variable infos. If we're producing new variables only for some - // variables, we'll need to do something like `(Type a, b, c)`. In that case, we'll use 'var' if the - // type is a built-in type, and varForBuiltInTypes is true. Otherwise, if it's not built-in, we'll - // use "use var elsewhere" to determine what to do. - var varForBuiltInTypes = ((CSharpSimplifierOptions)this.ExtractMethodGenerationOptions.SimplifierOptions).VarForBuiltInTypes.Value; - var varElsewhere = ((CSharpSimplifierOptions)this.ExtractMethodGenerationOptions.SimplifierOptions).VarElsewhere.Value; - - ExpressionSyntax left; - if (variableInfos.All(i => i.ReturnBehavior == ReturnBehavior.Initialization) && varElsewhere) - { - left = DeclarationExpression( - type: IdentifierName("var"), - ParenthesizedVariableDesignation( - [.. variableInfos.Select(v => SingleVariableDesignation(v.Name.ToIdentifierToken()))])); + var equalsValueClause = initialValue == null ? null : EqualsValueClause(value: initialValue); + + return LocalDeclarationStatement( + VariableDeclaration(typeNode) + .AddVariables(VariableDeclarator(singleVariable.Name.ToIdentifierToken()) + .WithInitializer(equalsValueClause))) + .WithUsingKeyword(usingKeyword); } else { - left = TupleExpression( - [.. variableInfos.Select(v => Argument(v.ReturnBehavior == ReturnBehavior.Initialization + // Note, we do not use "Use var when apparent" here as no types are apparent when doing `... = + // NewMethod()`. If we have `use var elsewhere` we may try to generate `var (a, b, c)` if we're + // producing new variables for all variable infos. If we're producing new variables only for some + // variables, we'll need to do something like `(Type a, b, c)`. In that case, we'll use 'var' if the + // type is a built-in type, and varForBuiltInTypes is true. Otherwise, if it's not built-in, we'll + // use "use var elsewhere" to determine what to do. + var varForBuiltInTypes = ((CSharpSimplifierOptions)this.ExtractMethodGenerationOptions.SimplifierOptions).VarForBuiltInTypes.Value; + var varElsewhere = ((CSharpSimplifierOptions)this.ExtractMethodGenerationOptions.SimplifierOptions).VarElsewhere.Value; + + ExpressionSyntax left; + if (variableInfos.All(i => i.ReturnBehavior == ReturnBehavior.Initialization) && varElsewhere) + { + left = DeclarationExpression( + type: IdentifierName("var"), + ParenthesizedVariableDesignation( + [.. variableInfos.Select(v => SingleVariableDesignation(v.Name.ToIdentifierToken()))])); + } + else + { + left = TupleExpression( + [.. variableInfos.Select(v => Argument(v.ReturnBehavior == ReturnBehavior.Initialization ? DeclarationExpression(v.GetVariableType().GenerateTypeSyntax(), SingleVariableDesignation(v.Name.ToIdentifierToken())) : v.Name.ToIdentifierName()))]); - } + } - return ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, initialValue)); + return ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, initialValue)); + } } - } - protected override async Task CreateGeneratedCodeAsync( - SemanticDocument newDocument, CancellationToken cancellationToken) - { - // in hybrid code cases such as extract method, formatter will have some difficulties on where it breaks lines in two. - // here, we explicitly insert newline at the end of "{" of auto generated method decl so that anchor knows how to find out - // indentation of inserted statements (from users code) with user code style preserved - var root = newDocument.Root; - var methodDefinition = root.GetAnnotatedNodes(MethodDefinitionAnnotation).First(); - - SyntaxNode newMethodDefinition = methodDefinition switch + protected override async Task CreateGeneratedCodeAsync( + SemanticDocument newDocument, CancellationToken cancellationToken) { - MethodDeclarationSyntax method => TweakNewLinesInMethod(method), - LocalFunctionStatementSyntax localFunction => TweakNewLinesInMethod(localFunction), - _ => throw new NotSupportedException("SyntaxNode expected to be MethodDeclarationSyntax or LocalFunctionStatementSyntax."), - }; - - newDocument = await newDocument.WithSyntaxRootAsync( - root.ReplaceNode(methodDefinition, newMethodDefinition), cancellationToken).ConfigureAwait(false); + // in hybrid code cases such as extract method, formatter will have some difficulties on where it breaks lines in two. + // here, we explicitly insert newline at the end of "{" of auto generated method decl so that anchor knows how to find out + // indentation of inserted statements (from users code) with user code style preserved + var root = newDocument.Root; + var methodDefinition = root.GetAnnotatedNodes(MethodDefinitionAnnotation).First(); - return await base.CreateGeneratedCodeAsync(newDocument, cancellationToken).ConfigureAwait(false); - } - - private static MethodDeclarationSyntax TweakNewLinesInMethod(MethodDeclarationSyntax method) - => TweakNewLinesInMethod(method, method.Body, method.ExpressionBody); + SyntaxNode newMethodDefinition = methodDefinition switch + { + MethodDeclarationSyntax method => TweakNewLinesInMethod(method), + LocalFunctionStatementSyntax localFunction => TweakNewLinesInMethod(localFunction), + _ => throw new NotSupportedException("SyntaxNode expected to be MethodDeclarationSyntax or LocalFunctionStatementSyntax."), + }; - private static LocalFunctionStatementSyntax TweakNewLinesInMethod(LocalFunctionStatementSyntax method) - => TweakNewLinesInMethod(method, method.Body, method.ExpressionBody); + newDocument = await newDocument.WithSyntaxRootAsync( + root.ReplaceNode(methodDefinition, newMethodDefinition), cancellationToken).ConfigureAwait(false); - private static TDeclarationNode TweakNewLinesInMethod(TDeclarationNode method, BlockSyntax body, ArrowExpressionClauseSyntax expressionBody) where TDeclarationNode : SyntaxNode - { - if (body != null) - { - return method.ReplaceToken( - body.OpenBraceToken, - body.OpenBraceToken.WithAppendedTrailingTrivia( - ElasticCarriageReturnLineFeed)); - } - else if (expressionBody != null) - { - return method.ReplaceToken( - expressionBody.ArrowToken, - expressionBody.ArrowToken.WithPrependedLeadingTrivia( - ElasticCarriageReturnLineFeed)); - } - else - { - return method; + return await base.CreateGeneratedCodeAsync(newDocument, cancellationToken).ConfigureAwait(false); } - } - - protected override async Task UpdateMethodAfterGenerationAsync( - SemanticDocument originalDocument, - IMethodSymbol methodSymbol, - CancellationToken cancellationToken) - { - // Only need to update for nullable reference types in return - if (methodSymbol.ReturnType.NullableAnnotation != NullableAnnotation.Annotated) - return originalDocument; - var syntaxNode = originalDocument.Root.GetAnnotatedNodesAndTokens(MethodDefinitionAnnotation).FirstOrDefault().AsNode(); - var nodeIsMethodOrLocalFunction = syntaxNode is MethodDeclarationSyntax or LocalFunctionStatementSyntax; - if (!nodeIsMethodOrLocalFunction) - return originalDocument; + private static MethodDeclarationSyntax TweakNewLinesInMethod(MethodDeclarationSyntax method) + => TweakNewLinesInMethod(method, method.Body, method.ExpressionBody); - var nullableReturnOperations = CheckReturnOperations(syntaxNode, originalDocument, cancellationToken); - if (nullableReturnOperations is not null) - return nullableReturnOperations; + private static LocalFunctionStatementSyntax TweakNewLinesInMethod(LocalFunctionStatementSyntax method) + => TweakNewLinesInMethod(method, method.Body, method.ExpressionBody); - var returnType = syntaxNode is MethodDeclarationSyntax method ? method.ReturnType : ((LocalFunctionStatementSyntax)syntaxNode).ReturnType; - var newDocument = await GenerateNewDocumentAsync(methodSymbol, returnType, originalDocument, cancellationToken).ConfigureAwait(false); - - return await SemanticDocument.CreateAsync(newDocument, cancellationToken).ConfigureAwait(false); - - static bool ReturnOperationBelongsToMethod(SyntaxNode returnOperationSyntax, SyntaxNode methodSyntax) + private static TDeclarationNode TweakNewLinesInMethod(TDeclarationNode method, BlockSyntax body, ArrowExpressionClauseSyntax expressionBody) where TDeclarationNode : SyntaxNode { - var enclosingMethod = returnOperationSyntax.FirstAncestorOrSelf(n => n switch + if (body != null) { - BaseMethodDeclarationSyntax _ => true, - AnonymousFunctionExpressionSyntax _ => true, - LocalFunctionStatementSyntax _ => true, - _ => false - }); - - return enclosingMethod == methodSyntax; + return method.ReplaceToken( + body.OpenBraceToken, + body.OpenBraceToken.WithAppendedTrailingTrivia( + ElasticCarriageReturnLineFeed)); + } + else if (expressionBody != null) + { + return method.ReplaceToken( + expressionBody.ArrowToken, + expressionBody.ArrowToken.WithPrependedLeadingTrivia( + ElasticCarriageReturnLineFeed)); + } + else + { + return method; + } } - static SemanticDocument CheckReturnOperations( - SyntaxNode node, + protected override async Task UpdateMethodAfterGenerationAsync( SemanticDocument originalDocument, + IMethodSymbol methodSymbol, CancellationToken cancellationToken) { - var semanticModel = originalDocument.SemanticModel; + // Only need to update for nullable reference types in return + if (methodSymbol.ReturnType.NullableAnnotation != NullableAnnotation.Annotated) + return originalDocument; + + var syntaxNode = originalDocument.Root.GetAnnotatedNodesAndTokens(MethodDefinitionAnnotation).FirstOrDefault().AsNode(); + var nodeIsMethodOrLocalFunction = syntaxNode is MethodDeclarationSyntax or LocalFunctionStatementSyntax; + if (!nodeIsMethodOrLocalFunction) + return originalDocument; + + var nullableReturnOperations = CheckReturnOperations(syntaxNode, originalDocument, cancellationToken); + if (nullableReturnOperations is not null) + return nullableReturnOperations; + + var returnType = syntaxNode is MethodDeclarationSyntax method ? method.ReturnType : ((LocalFunctionStatementSyntax)syntaxNode).ReturnType; + var newDocument = await GenerateNewDocumentAsync(methodSymbol, returnType, originalDocument, cancellationToken).ConfigureAwait(false); - var methodOperation = semanticModel.GetOperation(node, cancellationToken); - var returnOperations = methodOperation.DescendantsAndSelf().OfType(); + return await SemanticDocument.CreateAsync(newDocument, cancellationToken).ConfigureAwait(false); - foreach (var returnOperation in returnOperations) + static bool ReturnOperationBelongsToMethod(SyntaxNode returnOperationSyntax, SyntaxNode methodSyntax) { - // If the return statement is located in a nested local function or lambda it - // shouldn't contribute to the nullability of the extracted method's return type - if (!ReturnOperationBelongsToMethod(returnOperation.Syntax, methodOperation.Syntax)) - continue; + var enclosingMethod = returnOperationSyntax.FirstAncestorOrSelf(n => n switch + { + BaseMethodDeclarationSyntax _ => true, + AnonymousFunctionExpressionSyntax _ => true, + LocalFunctionStatementSyntax _ => true, + _ => false + }); + + return enclosingMethod == methodSyntax; + } - var syntax = returnOperation.ReturnedValue?.Syntax ?? returnOperation.Syntax; - var returnTypeInfo = semanticModel.GetTypeInfo(syntax, cancellationToken); - if (returnTypeInfo.Nullability.FlowState == NullableFlowState.MaybeNull) + static SemanticDocument CheckReturnOperations( + SyntaxNode node, + SemanticDocument originalDocument, + CancellationToken cancellationToken) + { + var semanticModel = originalDocument.SemanticModel; + + var methodOperation = semanticModel.GetOperation(node, cancellationToken); + var returnOperations = methodOperation.DescendantsAndSelf().OfType(); + + foreach (var returnOperation in returnOperations) { - // Flow state shows that return is correctly nullable - return originalDocument; + // If the return statement is located in a nested local function or lambda it + // shouldn't contribute to the nullability of the extracted method's return type + if (!ReturnOperationBelongsToMethod(returnOperation.Syntax, methodOperation.Syntax)) + continue; + + var syntax = returnOperation.ReturnedValue?.Syntax ?? returnOperation.Syntax; + var returnTypeInfo = semanticModel.GetTypeInfo(syntax, cancellationToken); + if (returnTypeInfo.Nullability.FlowState == NullableFlowState.MaybeNull) + { + // Flow state shows that return is correctly nullable + return originalDocument; + } } - } - return null; - } + return null; + } - static async Task GenerateNewDocumentAsync( - IMethodSymbol methodSymbol, - TypeSyntax returnType, - SemanticDocument originalDocument, - CancellationToken cancellationToken) - { - // Return type can be updated to not be null - var newType = methodSymbol.ReturnType.WithNullableAnnotation(NullableAnnotation.NotAnnotated); + static async Task GenerateNewDocumentAsync( + IMethodSymbol methodSymbol, + TypeSyntax returnType, + SemanticDocument originalDocument, + CancellationToken cancellationToken) + { + // Return type can be updated to not be null + var newType = methodSymbol.ReturnType.WithNullableAnnotation(NullableAnnotation.NotAnnotated); - var oldRoot = await originalDocument.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var newRoot = oldRoot.ReplaceNode(returnType, newType.GenerateTypeSyntax()); + var oldRoot = await originalDocument.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = oldRoot.ReplaceNode(returnType, newType.GenerateTypeSyntax(allowVar: false)); - return originalDocument.Document.WithSyntaxRoot(newRoot); + return originalDocument.Document.WithSyntaxRoot(newRoot); + } } - } - - protected SyntaxToken GenerateMethodNameForStatementGenerators() - { - var semanticModel = SemanticDocument.SemanticModel; - var nameGenerator = new UniqueNameGenerator(semanticModel); - var scope = this.SelectionResult.GetContainingScope(); - // If extracting a local function, we want to ensure all local variables are considered when generating a unique name. - if (LocalFunction) + protected SyntaxToken GenerateMethodNameForStatementGenerators() { - scope = this.SelectionResult.GetFirstTokenInSelection().Parent; - } + var semanticModel = SemanticDocument.SemanticModel; + var nameGenerator = new UniqueNameGenerator(semanticModel); + var scope = this.SelectionResult.GetContainingScope(); - return Identifier(nameGenerator.CreateUniqueMethodName(scope, GenerateMethodNameFromUserPreference())); - } + // If extracting a local function, we want to ensure all local variables are considered when generating a unique name. + if (LocalFunction) + { + scope = this.SelectionResult.GetFirstTokenInSelection().Parent; + } - protected string GenerateMethodNameFromUserPreference() - { - var methodName = NewMethodPascalCaseStr; - if (!LocalFunction) - { - return methodName; + return Identifier(nameGenerator.CreateUniqueMethodName(scope, GenerateMethodNameFromUserPreference())); } - // For local functions, pascal case and camel case should be the most common and therefore we only consider those cases. - var localFunctionPreferences = Options.NamingStyle.SymbolSpecifications.Where(symbol => symbol.AppliesTo(new SymbolSpecification.SymbolKindOrTypeKind(MethodKind.LocalFunction), CreateMethodModifiers(), null)); - - var namingRules = Options.NamingStyle.Rules.NamingRules; - var localFunctionKind = new SymbolSpecification.SymbolKindOrTypeKind(MethodKind.LocalFunction); - if (LocalFunction) + protected string GenerateMethodNameFromUserPreference() { - if (namingRules.Any(static (rule, arg) => rule.NamingStyle.CapitalizationScheme.Equals(Capitalization.CamelCase) && rule.SymbolSpecification.AppliesTo(arg.localFunctionKind, arg.self.CreateMethodModifiers(), null), (self: this, localFunctionKind))) + var methodName = NewMethodPascalCaseStr; + if (!LocalFunction) { - methodName = NewMethodCamelCaseStr; + return methodName; } - } - // We default to pascal case. - return methodName; + // For local functions, pascal case and camel case should be the most common and therefore we only consider those cases. + var localFunctionPreferences = Options.NamingStyle.SymbolSpecifications.Where(symbol => symbol.AppliesTo(new SymbolSpecification.SymbolKindOrTypeKind(MethodKind.LocalFunction), CreateMethodModifiers(), null)); + + var namingRules = Options.NamingStyle.Rules.NamingRules; + var localFunctionKind = new SymbolSpecification.SymbolKindOrTypeKind(MethodKind.LocalFunction); + if (LocalFunction) + { + if (namingRules.Any(static (rule, arg) => rule.NamingStyle.CapitalizationScheme.Equals(Capitalization.CamelCase) && rule.SymbolSpecification.AppliesTo(arg.localFunctionKind, arg.self.CreateMethodModifiers(), null), (self: this, localFunctionKind))) + { + methodName = NewMethodCamelCaseStr; + } + } + + // We default to pascal case. + return methodName; + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.FormattingProvider.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.FormattingProvider.cs index 794c3b30049ef..12400bfc471a0 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.FormattingProvider.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.FormattingProvider.cs @@ -7,62 +7,65 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private sealed class FormattingRule : AbstractFormattingRule + internal sealed partial class CSharpMethodExtractor { - public static readonly FormattingRule Instance = new(); - - private FormattingRule() + private sealed class FormattingRule : AbstractFormattingRule { - } + public static readonly FormattingRule Instance = new(); - public override AdjustNewLinesOperation? GetAdjustNewLinesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation) - { - // for extract method case, for a hybrid case, don't force rule, but preserve user style - var operation = base.GetAdjustNewLinesOperation(in previousToken, in currentToken, in nextOperation); - if (operation == null) + private FormattingRule() { - return null; } - if (operation.Option == AdjustNewLinesOption.ForceLinesIfOnSingleLine) + public override AdjustNewLinesOperation? GetAdjustNewLinesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation) { - return FormattingOperations.CreateAdjustNewLinesOperation(operation.Line, AdjustNewLinesOption.PreserveLines); - } + // for extract method case, for a hybrid case, don't force rule, but preserve user style + var operation = base.GetAdjustNewLinesOperation(in previousToken, in currentToken, in nextOperation); + if (operation == null) + { + return null; + } - if (operation.Option != AdjustNewLinesOption.ForceLines) - { - return operation; - } + if (operation.Option == AdjustNewLinesOption.ForceLinesIfOnSingleLine) + { + return FormattingOperations.CreateAdjustNewLinesOperation(operation.Line, AdjustNewLinesOption.PreserveLines); + } - if (previousToken.RawKind == (int)SyntaxKind.OpenBraceToken) - { - return operation; - } + if (operation.Option != AdjustNewLinesOption.ForceLines) + { + return operation; + } - if (previousToken.BetweenFieldAndNonFieldMember(currentToken)) - { - // make sure to have at least 2 line breaks between field and other members except field - return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.PreserveLines); - } + if (previousToken.RawKind == (int)SyntaxKind.OpenBraceToken) + { + return operation; + } - if (previousToken.HasHybridTriviaBetween(currentToken)) - { - return FormattingOperations.CreateAdjustNewLinesOperation(operation.Line, AdjustNewLinesOption.PreserveLines); - } + if (previousToken.BetweenFieldAndNonFieldMember(currentToken)) + { + // make sure to have at least 2 line breaks between field and other members except field + return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.PreserveLines); + } - return operation; - } + if (previousToken.HasHybridTriviaBetween(currentToken)) + { + return FormattingOperations.CreateAdjustNewLinesOperation(operation.Line, AdjustNewLinesOption.PreserveLines); + } - public override void AddAnchorIndentationOperations(List list, SyntaxNode node, in NextAnchorIndentationOperationAction nextOperation) - { - if (node.Kind() is SyntaxKind.SimpleLambdaExpression or SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.AnonymousMethodExpression) - { - return; + return operation; } - nextOperation.Invoke(); + public override void AddAnchorIndentationOperations(List list, SyntaxNode node, in NextAnchorIndentationOperationAction nextOperation) + { + if (node.Kind() is SyntaxKind.SimpleLambdaExpression or SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.AnonymousMethodExpression) + { + return; + } + + nextOperation.Invoke(); + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs index 3cfd3c5a0dec0..9db6e792fefae 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs @@ -16,290 +16,293 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; using static SyntaxFactory; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private sealed class PostProcessor + internal sealed partial class CSharpMethodExtractor { - private readonly SemanticModel _semanticModel; - private readonly int _contextPosition; - - public PostProcessor(SemanticModel semanticModel, int contextPosition) + private sealed class PostProcessor { - Contract.ThrowIfNull(semanticModel); - - _semanticModel = semanticModel; - _contextPosition = contextPosition; - } + private readonly SemanticModel _semanticModel; + private readonly int _contextPosition; - public static ImmutableArray RemoveRedundantBlock(ImmutableArray statements) - { - // it must have only one statement - if (statements.Count() != 1) + public PostProcessor(SemanticModel semanticModel, int contextPosition) { - return statements; + Contract.ThrowIfNull(semanticModel); + + _semanticModel = semanticModel; + _contextPosition = contextPosition; } - // that statement must be a block - if (statements.Single() is not BlockSyntax block) + public static ImmutableArray RemoveRedundantBlock(ImmutableArray statements) { - return statements; - } + // it must have only one statement + if (statements.Count() != 1) + { + return statements; + } - // we have a block, remove them - return RemoveRedundantBlock(block); - } + // that statement must be a block + if (statements.Single() is not BlockSyntax block) + { + return statements; + } - private static ImmutableArray RemoveRedundantBlock(BlockSyntax block) - { - // if block doesn't have any statement - if (block.Statements.Count == 0) - { - // either remove the block if it doesn't have any trivia, or return as it is if - // there are trivia attached to block - return (block.OpenBraceToken.GetAllTrivia().IsEmpty() && block.CloseBraceToken.GetAllTrivia().IsEmpty()) - ? [] - : [block]; + // we have a block, remove them + return RemoveRedundantBlock(block); } - // okay transfer asset attached to block to statements - var firstStatement = block.Statements.First(); - var firstToken = firstStatement.GetFirstToken(includeZeroWidth: true); - var firstTokenWithAsset = block.OpenBraceToken.CopyAnnotationsTo(firstToken).WithPrependedLeadingTrivia(block.OpenBraceToken.GetAllTrivia()); + private static ImmutableArray RemoveRedundantBlock(BlockSyntax block) + { + // if block doesn't have any statement + if (block.Statements.Count == 0) + { + // either remove the block if it doesn't have any trivia, or return as it is if + // there are trivia attached to block + return (block.OpenBraceToken.GetAllTrivia().IsEmpty() && block.CloseBraceToken.GetAllTrivia().IsEmpty()) + ? [] + : [block]; + } - var lastStatement = block.Statements.Last(); - var lastToken = lastStatement.GetLastToken(includeZeroWidth: true); - var lastTokenWithAsset = block.CloseBraceToken.CopyAnnotationsTo(lastToken).WithAppendedTrailingTrivia(block.CloseBraceToken.GetAllTrivia()); + // okay transfer asset attached to block to statements + var firstStatement = block.Statements.First(); + var firstToken = firstStatement.GetFirstToken(includeZeroWidth: true); + var firstTokenWithAsset = block.OpenBraceToken.CopyAnnotationsTo(firstToken).WithPrependedLeadingTrivia(block.OpenBraceToken.GetAllTrivia()); - // create new block with new tokens - block = block.ReplaceTokens([firstToken, lastToken], (o, c) => (o == firstToken) ? firstTokenWithAsset : lastTokenWithAsset); + var lastStatement = block.Statements.Last(); + var lastToken = lastStatement.GetLastToken(includeZeroWidth: true); + var lastTokenWithAsset = block.CloseBraceToken.CopyAnnotationsTo(lastToken).WithAppendedTrailingTrivia(block.CloseBraceToken.GetAllTrivia()); - // return only statements without the wrapping block - return [.. block.Statements]; - } + // create new block with new tokens + block = block.ReplaceTokens([firstToken, lastToken], (o, c) => (o == firstToken) ? firstTokenWithAsset : lastTokenWithAsset); - public ImmutableArray MergeDeclarationStatements(ImmutableArray statements) - { - if (statements.FirstOrDefault() == null) - { - return statements; + // return only statements without the wrapping block + return [.. block.Statements]; } - return MergeDeclarationStatementsWorker(statements); - } - - private ImmutableArray MergeDeclarationStatementsWorker(ImmutableArray statements) - { - using var _ = ArrayBuilder.GetInstance(out var result); - - var map = new Dictionary>(); - foreach (var statement in statements) + public ImmutableArray MergeDeclarationStatements(ImmutableArray statements) { - if (!IsDeclarationMergable(statement)) + if (statements.FirstOrDefault() == null) { - result.AddRange(GetMergedDeclarationStatements(map)); - result.Add(statement); - continue; + return statements; } - AppendDeclarationStatementToMap(statement as LocalDeclarationStatementSyntax, map); + return MergeDeclarationStatementsWorker(statements); } - // merge leftover - if (map.Count > 0) - result.AddRange(GetMergedDeclarationStatements(map)); + private ImmutableArray MergeDeclarationStatementsWorker(ImmutableArray statements) + { + using var _ = ArrayBuilder.GetInstance(out var result); - return result.ToImmutableAndClear(); - } + var map = new Dictionary>(); + foreach (var statement in statements) + { + if (!IsDeclarationMergable(statement)) + { + result.AddRange(GetMergedDeclarationStatements(map)); + result.Add(statement); + continue; + } - private void AppendDeclarationStatementToMap( - LocalDeclarationStatementSyntax statement, - Dictionary> map) - { - Contract.ThrowIfNull(statement); + AppendDeclarationStatementToMap(statement as LocalDeclarationStatementSyntax, map); + } - var type = _semanticModel.GetSpeculativeTypeInfo(_contextPosition, statement.Declaration.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; - Contract.ThrowIfNull(type); + // merge leftover + if (map.Count > 0) + result.AddRange(GetMergedDeclarationStatements(map)); - map.GetOrAdd(type, _ => []).Add(statement); - } + return result.ToImmutableAndClear(); + } - private static IEnumerable GetMergedDeclarationStatements( - Dictionary> map) - { - foreach (var keyValuePair in map) + private void AppendDeclarationStatementToMap( + LocalDeclarationStatementSyntax statement, + Dictionary> map) { - Contract.ThrowIfFalse(keyValuePair.Value.Count > 0); + Contract.ThrowIfNull(statement); + + var type = _semanticModel.GetSpeculativeTypeInfo(_contextPosition, statement.Declaration.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; + Contract.ThrowIfNull(type); + + map.GetOrAdd(type, _ => []).Add(statement); + } - // merge all variable decl for current type - var variables = new List(); - foreach (var statement in keyValuePair.Value) + private static IEnumerable GetMergedDeclarationStatements( + Dictionary> map) + { + foreach (var keyValuePair in map) { - foreach (var variable in statement.Declaration.Variables) + Contract.ThrowIfFalse(keyValuePair.Value.Count > 0); + + // merge all variable decl for current type + var variables = new List(); + foreach (var statement in keyValuePair.Value) { - variables.Add(variable); + foreach (var variable in statement.Declaration.Variables) + { + variables.Add(variable); + } } + + // and create one decl statement + // use type name from the first decl statement + yield return + LocalDeclarationStatement( + VariableDeclaration(keyValuePair.Value.First().Declaration.Type, [.. variables])); } - // and create one decl statement - // use type name from the first decl statement - yield return - LocalDeclarationStatement( - VariableDeclaration(keyValuePair.Value.First().Declaration.Type, [.. variables])); + map.Clear(); } - map.Clear(); - } + private bool IsDeclarationMergable(StatementSyntax statement) + { + Contract.ThrowIfNull(statement); - private bool IsDeclarationMergable(StatementSyntax statement) - { - Contract.ThrowIfNull(statement); + // to be mergable, statement must be + // 1. decl statement without any extra info + // 2. no initialization on any of its decls + // 3. no trivia except whitespace + // 4. type must be known - // to be mergable, statement must be - // 1. decl statement without any extra info - // 2. no initialization on any of its decls - // 3. no trivia except whitespace - // 4. type must be known + if (statement is not LocalDeclarationStatementSyntax declarationStatement) + { + return false; + } - if (statement is not LocalDeclarationStatementSyntax declarationStatement) - { - return false; - } + if (declarationStatement.Modifiers.Count > 0 || + declarationStatement.IsConst || + declarationStatement.IsMissing) + { + return false; + } - if (declarationStatement.Modifiers.Count > 0 || - declarationStatement.IsConst || - declarationStatement.IsMissing) - { - return false; - } + if (ContainsAnyInitialization(declarationStatement)) + { + return false; + } - if (ContainsAnyInitialization(declarationStatement)) - { - return false; - } + if (!ContainsOnlyWhitespaceTrivia(declarationStatement)) + { + return false; + } - if (!ContainsOnlyWhitespaceTrivia(declarationStatement)) - { - return false; - } + var semanticInfo = _semanticModel.GetSpeculativeTypeInfo(_contextPosition, declarationStatement.Declaration.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; + if (semanticInfo == null || + semanticInfo.TypeKind is TypeKind.Error or TypeKind.Unknown) + { + return false; + } - var semanticInfo = _semanticModel.GetSpeculativeTypeInfo(_contextPosition, declarationStatement.Declaration.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; - if (semanticInfo == null || - semanticInfo.TypeKind is TypeKind.Error or TypeKind.Unknown) - { - return false; + return true; } - return true; - } - - private static bool ContainsAnyInitialization(LocalDeclarationStatementSyntax statement) - { - foreach (var variable in statement.Declaration.Variables) + private static bool ContainsAnyInitialization(LocalDeclarationStatementSyntax statement) { - if (variable.Initializer != null) + foreach (var variable in statement.Declaration.Variables) { - return true; + if (variable.Initializer != null) + { + return true; + } } - } - return false; - } + return false; + } - private static bool ContainsOnlyWhitespaceTrivia(StatementSyntax statement) - { - foreach (var token in statement.DescendantTokens()) + private static bool ContainsOnlyWhitespaceTrivia(StatementSyntax statement) { - foreach (var trivia in token.LeadingTrivia.Concat(token.TrailingTrivia)) + foreach (var token in statement.DescendantTokens()) { - if (trivia.Kind() is not SyntaxKind.WhitespaceTrivia and - not SyntaxKind.EndOfLineTrivia) + foreach (var trivia in token.LeadingTrivia.Concat(token.TrailingTrivia)) { - return false; + if (trivia.Kind() is not SyntaxKind.WhitespaceTrivia and + not SyntaxKind.EndOfLineTrivia) + { + return false; + } } } - } - - return true; - } - public static ImmutableArray RemoveInitializedDeclarationAndReturnPattern(ImmutableArray statements) - { - // if we have inline temp variable as service, we could just use that service here. - // since it is not a service right now, do very simple clean up - if (statements.ElementAtOrDefault(2) != null) - { - return statements; + return true; } - if (statements.ElementAtOrDefault(0) is not LocalDeclarationStatementSyntax declaration || statements.ElementAtOrDefault(1) is not ReturnStatementSyntax returnStatement) + public static ImmutableArray RemoveInitializedDeclarationAndReturnPattern(ImmutableArray statements) { - return statements; - } + // if we have inline temp variable as service, we could just use that service here. + // since it is not a service right now, do very simple clean up + if (statements.ElementAtOrDefault(2) != null) + { + return statements; + } - if (declaration.Declaration == null || - declaration.Declaration.Variables.Count != 1 || - declaration.Declaration.Variables[0].Initializer == null || - declaration.Declaration.Variables[0].Initializer.Value == null || - declaration.Declaration.Variables[0].Initializer.Value is StackAllocArrayCreationExpressionSyntax || - returnStatement.Expression == null) - { - return statements; - } + if (statements.ElementAtOrDefault(0) is not LocalDeclarationStatementSyntax declaration || statements.ElementAtOrDefault(1) is not ReturnStatementSyntax returnStatement) + { + return statements; + } - if (!ContainsOnlyWhitespaceTrivia(declaration) || - !ContainsOnlyWhitespaceTrivia(returnStatement)) - { - return statements; - } + if (declaration.Declaration == null || + declaration.Declaration.Variables.Count != 1 || + declaration.Declaration.Variables[0].Initializer == null || + declaration.Declaration.Variables[0].Initializer.Value == null || + declaration.Declaration.Variables[0].Initializer.Value is StackAllocArrayCreationExpressionSyntax || + returnStatement.Expression == null) + { + return statements; + } - var variableName = declaration.Declaration.Variables[0].Identifier.ToString(); - if (returnStatement.Expression.ToString() != variableName) - { - return statements; - } + if (!ContainsOnlyWhitespaceTrivia(declaration) || + !ContainsOnlyWhitespaceTrivia(returnStatement)) + { + return statements; + } - return [ReturnStatement(declaration.Declaration.Variables[0].Initializer.Value)]; - } + var variableName = declaration.Declaration.Variables[0].Identifier.ToString(); + if (returnStatement.Expression.ToString() != variableName) + { + return statements; + } - public static ImmutableArray RemoveDeclarationAssignmentPattern(ImmutableArray statements) - { - if (statements.ElementAtOrDefault(0) is not LocalDeclarationStatementSyntax declaration || statements.ElementAtOrDefault(1) is not ExpressionStatementSyntax assignment) - { - return statements; + return [ReturnStatement(declaration.Declaration.Variables[0].Initializer.Value)]; } - if (ContainsAnyInitialization(declaration) || - declaration.Declaration == null || - declaration.Declaration.Variables.Count != 1 || - assignment.Expression == null || - assignment.Expression.Kind() != SyntaxKind.SimpleAssignmentExpression) + public static ImmutableArray RemoveDeclarationAssignmentPattern(ImmutableArray statements) { - return statements; - } + if (statements.ElementAtOrDefault(0) is not LocalDeclarationStatementSyntax declaration || statements.ElementAtOrDefault(1) is not ExpressionStatementSyntax assignment) + { + return statements; + } - if (!ContainsOnlyWhitespaceTrivia(declaration) || - !ContainsOnlyWhitespaceTrivia(assignment)) - { - return statements; - } + if (ContainsAnyInitialization(declaration) || + declaration.Declaration == null || + declaration.Declaration.Variables.Count != 1 || + assignment.Expression == null || + assignment.Expression.Kind() != SyntaxKind.SimpleAssignmentExpression) + { + return statements; + } + + if (!ContainsOnlyWhitespaceTrivia(declaration) || + !ContainsOnlyWhitespaceTrivia(assignment)) + { + return statements; + } - var variableName = declaration.Declaration.Variables[0].Identifier.ToString(); + var variableName = declaration.Declaration.Variables[0].Identifier.ToString(); - var assignmentExpression = assignment.Expression as AssignmentExpressionSyntax; - if (assignmentExpression.Left == null || - assignmentExpression.Right == null || - assignmentExpression.Left.ToString() != variableName) - { - return statements; - } + var assignmentExpression = assignment.Expression as AssignmentExpressionSyntax; + if (assignmentExpression.Left == null || + assignmentExpression.Right == null || + assignmentExpression.Left.ToString() != variableName) + { + return statements; + } - var variable = declaration.Declaration.Variables[0].WithInitializer(EqualsValueClause(assignmentExpression.Right)); - return - [ - declaration.WithDeclaration( + var variable = declaration.Declaration.Variables[0].WithInitializer(EqualsValueClause(assignmentExpression.Right)); + return + [ + declaration.WithDeclaration( declaration.Declaration.WithVariables([variable])), - .. statements.Skip(2), - ]; + .. statements.Skip(2), + ]; + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.TriviaResult.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.TriviaResult.cs index da750638a4247..1999ed6bae3c6 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.TriviaResult.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.TriviaResult.cs @@ -15,165 +15,168 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpMethodExtractor +internal sealed partial class CSharpExtractMethodService { - private sealed class CSharpTriviaResult : TriviaResult + internal sealed partial class CSharpMethodExtractor { - public static async Task ProcessAsync(CSharpSelectionResult selectionResult, CancellationToken cancellationToken) + private sealed class CSharpTriviaResult : TriviaResult { - var preservationService = selectionResult.SemanticDocument.Document.Project.Services.GetService(); - var root = selectionResult.SemanticDocument.Root; - var result = preservationService.SaveTriviaAroundSelection(root, selectionResult.FinalSpan); - return new CSharpTriviaResult( - await selectionResult.SemanticDocument.WithSyntaxRootAsync(result.Root, cancellationToken).ConfigureAwait(false), - result); - } - - private CSharpTriviaResult(SemanticDocument document, ITriviaSavedResult result) - : base(document, result, (int)SyntaxKind.EndOfLineTrivia, (int)SyntaxKind.WhitespaceTrivia) - { - } - - protected override AnnotationResolver GetAnnotationResolver(SyntaxNode callsite, SyntaxNode method) - { - var isMethodOrLocalFunction = method is MethodDeclarationSyntax or LocalFunctionStatementSyntax; - if (callsite == null || !isMethodOrLocalFunction) + public static async Task ProcessAsync(CSharpSelectionResult selectionResult, CancellationToken cancellationToken) { - return null; + var preservationService = selectionResult.SemanticDocument.Document.Project.Services.GetService(); + var root = selectionResult.SemanticDocument.Root; + var result = preservationService.SaveTriviaAroundSelection(root, selectionResult.FinalSpan); + return new CSharpTriviaResult( + await selectionResult.SemanticDocument.WithSyntaxRootAsync(result.Root, cancellationToken).ConfigureAwait(false), + result); } - return (node, location, annotation) => AnnotationResolver(node, location, annotation, callsite, method); - } - - protected override TriviaResolver GetTriviaResolver(SyntaxNode method) - { - var isMethodOrLocalFunction = method is MethodDeclarationSyntax or LocalFunctionStatementSyntax; - if (!isMethodOrLocalFunction) + private CSharpTriviaResult(SemanticDocument document, ITriviaSavedResult result) + : base(document, result, (int)SyntaxKind.EndOfLineTrivia, (int)SyntaxKind.WhitespaceTrivia) { - return null; } - return (location, tokenPair, triviaMap) => TriviaResolver(location, tokenPair, triviaMap, method); - } - - private static SyntaxToken AnnotationResolver( - SyntaxNode node, - TriviaLocation location, - SyntaxAnnotation annotation, - SyntaxNode callsite, - SyntaxNode method) - { - var token = node.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault().AsToken(); - if (token.RawKind != 0) + protected override AnnotationResolver GetAnnotationResolver(SyntaxNode callsite, SyntaxNode method) { - return token; + var isMethodOrLocalFunction = method is MethodDeclarationSyntax or LocalFunctionStatementSyntax; + if (callsite == null || !isMethodOrLocalFunction) + { + return null; + } + + return (node, location, annotation) => AnnotationResolver(node, location, annotation, callsite, method); } - var (body, expressionBody, semicolonToken) = GetResolverElements(method); - return location switch + protected override TriviaResolver GetTriviaResolver(SyntaxNode method) { - TriviaLocation.BeforeBeginningOfSpan => callsite.GetFirstToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true), - TriviaLocation.AfterEndOfSpan => callsite.GetLastToken(includeZeroWidth: true).GetNextToken(includeZeroWidth: true), - TriviaLocation.AfterBeginningOfSpan => body != null - ? body.OpenBraceToken.GetNextToken(includeZeroWidth: true) - : expressionBody.ArrowToken.GetNextToken(includeZeroWidth: true), - TriviaLocation.BeforeEndOfSpan => body != null - ? body.CloseBraceToken.GetPreviousToken(includeZeroWidth: true) - : semicolonToken, - _ => throw ExceptionUtilities.UnexpectedValue(location) - }; - } - - private IEnumerable TriviaResolver( - TriviaLocation location, - PreviousNextTokenPair tokenPair, - Dictionary triviaMap, - SyntaxNode method) - { - // Resolve trivia at the edge of the selection. simple case is easy to deal with, but complex cases where - // elastic trivia and user trivia are mixed (hybrid case) and we want to preserve some part of user coding style - // but not others can be dealt with here. + var isMethodOrLocalFunction = method is MethodDeclarationSyntax or LocalFunctionStatementSyntax; + if (!isMethodOrLocalFunction) + { + return null; + } - var (body, expressionBody, semicolonToken) = GetResolverElements(method); + return (location, tokenPair, triviaMap) => TriviaResolver(location, tokenPair, triviaMap, method); + } - // method has no statement in them. so basically two trivia list now pointing to same thing. "{" and "}" - if (body != null) + private static SyntaxToken AnnotationResolver( + SyntaxNode node, + TriviaLocation location, + SyntaxAnnotation annotation, + SyntaxNode callsite, + SyntaxNode method) { - if (tokenPair.PreviousToken == body.OpenBraceToken && - tokenPair.NextToken == body.CloseBraceToken) + var token = node.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault().AsToken(); + if (token.RawKind != 0) { - return location == TriviaLocation.AfterBeginningOfSpan ? [SyntaxFactory.ElasticMarker] : []; + return token; } + + var (body, expressionBody, semicolonToken) = GetResolverElements(method); + return location switch + { + TriviaLocation.BeforeBeginningOfSpan => callsite.GetFirstToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true), + TriviaLocation.AfterEndOfSpan => callsite.GetLastToken(includeZeroWidth: true).GetNextToken(includeZeroWidth: true), + TriviaLocation.AfterBeginningOfSpan => body != null + ? body.OpenBraceToken.GetNextToken(includeZeroWidth: true) + : expressionBody.ArrowToken.GetNextToken(includeZeroWidth: true), + TriviaLocation.BeforeEndOfSpan => body != null + ? body.CloseBraceToken.GetPreviousToken(includeZeroWidth: true) + : semicolonToken, + _ => throw ExceptionUtilities.UnexpectedValue(location) + }; } - else + + private IEnumerable TriviaResolver( + TriviaLocation location, + PreviousNextTokenPair tokenPair, + Dictionary triviaMap, + SyntaxNode method) { - if (tokenPair.PreviousToken == expressionBody.ArrowToken && - tokenPair.NextToken.GetPreviousToken() == semicolonToken) + // Resolve trivia at the edge of the selection. simple case is easy to deal with, but complex cases where + // elastic trivia and user trivia are mixed (hybrid case) and we want to preserve some part of user coding style + // but not others can be dealt with here. + + var (body, expressionBody, semicolonToken) = GetResolverElements(method); + + // method has no statement in them. so basically two trivia list now pointing to same thing. "{" and "}" + if (body != null) { - return location == TriviaLocation.AfterBeginningOfSpan ? [SyntaxFactory.ElasticMarker] : []; + if (tokenPair.PreviousToken == body.OpenBraceToken && + tokenPair.NextToken == body.CloseBraceToken) + { + return location == TriviaLocation.AfterBeginningOfSpan ? [SyntaxFactory.ElasticMarker] : []; + } + } + else + { + if (tokenPair.PreviousToken == expressionBody.ArrowToken && + tokenPair.NextToken.GetPreviousToken() == semicolonToken) + { + return location == TriviaLocation.AfterBeginningOfSpan ? [SyntaxFactory.ElasticMarker] : []; + } } - } - triviaMap.TryGetValue(tokenPair.PreviousToken, out var previousTriviaPair); - var trailingTrivia = previousTriviaPair.TrailingTrivia ?? []; + triviaMap.TryGetValue(tokenPair.PreviousToken, out var previousTriviaPair); + var trailingTrivia = previousTriviaPair.TrailingTrivia ?? []; - triviaMap.TryGetValue(tokenPair.NextToken, out var nextTriviaPair); - var leadingTrivia = nextTriviaPair.LeadingTrivia ?? []; + triviaMap.TryGetValue(tokenPair.NextToken, out var nextTriviaPair); + var leadingTrivia = nextTriviaPair.LeadingTrivia ?? []; - var list = trailingTrivia.Concat(leadingTrivia); + var list = trailingTrivia.Concat(leadingTrivia); - return location switch + return location switch + { + TriviaLocation.BeforeBeginningOfSpan => FilterBeforeBeginningOfSpan(tokenPair, list), + TriviaLocation.AfterEndOfSpan => FilterTriviaList(list.Concat(tokenPair.NextToken.LeadingTrivia)), + TriviaLocation.AfterBeginningOfSpan => FilterTriviaList(AppendTrailingTrivia(tokenPair).Concat(list).Concat(tokenPair.NextToken.LeadingTrivia)), + TriviaLocation.BeforeEndOfSpan => FilterTriviaList(tokenPair.PreviousToken.TrailingTrivia.Concat(list).Concat(tokenPair.NextToken.LeadingTrivia)), + _ => throw ExceptionUtilities.UnexpectedValue(location), + }; + } + + private static (BlockSyntax body, ArrowExpressionClauseSyntax expressionBody, SyntaxToken semicolonToken) GetResolverElements(SyntaxNode method) { - TriviaLocation.BeforeBeginningOfSpan => FilterBeforeBeginningOfSpan(tokenPair, list), - TriviaLocation.AfterEndOfSpan => FilterTriviaList(list.Concat(tokenPair.NextToken.LeadingTrivia)), - TriviaLocation.AfterBeginningOfSpan => FilterTriviaList(AppendTrailingTrivia(tokenPair).Concat(list).Concat(tokenPair.NextToken.LeadingTrivia)), - TriviaLocation.BeforeEndOfSpan => FilterTriviaList(tokenPair.PreviousToken.TrailingTrivia.Concat(list).Concat(tokenPair.NextToken.LeadingTrivia)), - _ => throw ExceptionUtilities.UnexpectedValue(location), - }; - } + return method switch + { + MethodDeclarationSyntax methodDeclaration => (methodDeclaration.Body, methodDeclaration.ExpressionBody, methodDeclaration.SemicolonToken), + LocalFunctionStatementSyntax localFunctionDeclaration => (localFunctionDeclaration.Body, localFunctionDeclaration.ExpressionBody, localFunctionDeclaration.SemicolonToken), + _ => throw ExceptionUtilities.UnexpectedValue(method) + }; + } - private static (BlockSyntax body, ArrowExpressionClauseSyntax expressionBody, SyntaxToken semicolonToken) GetResolverElements(SyntaxNode method) - { - return method switch + private IEnumerable FilterBeforeBeginningOfSpan(PreviousNextTokenPair tokenPair, IEnumerable list) { - MethodDeclarationSyntax methodDeclaration => (methodDeclaration.Body, methodDeclaration.ExpressionBody, methodDeclaration.SemicolonToken), - LocalFunctionStatementSyntax localFunctionDeclaration => (localFunctionDeclaration.Body, localFunctionDeclaration.ExpressionBody, localFunctionDeclaration.SemicolonToken), - _ => throw ExceptionUtilities.UnexpectedValue(method) - }; - } + var allList = FilterTriviaList(tokenPair.PreviousToken.TrailingTrivia.Concat(list).Concat(AppendLeadingTrivia(tokenPair))); - private IEnumerable FilterBeforeBeginningOfSpan(PreviousNextTokenPair tokenPair, IEnumerable list) - { - var allList = FilterTriviaList(tokenPair.PreviousToken.TrailingTrivia.Concat(list).Concat(AppendLeadingTrivia(tokenPair))); + if (tokenPair.PreviousToken.RawKind == (int)SyntaxKind.OpenBraceToken) + { + return RemoveBlankLines(allList); + } - if (tokenPair.PreviousToken.RawKind == (int)SyntaxKind.OpenBraceToken) - { - return RemoveBlankLines(allList); + return allList; } - return allList; - } - - private static IEnumerable AppendLeadingTrivia(PreviousNextTokenPair tokenPair) - { - if (tokenPair.PreviousToken.RawKind is ((int)SyntaxKind.OpenBraceToken) or - ((int)SyntaxKind.SemicolonToken)) + private static IEnumerable AppendLeadingTrivia(PreviousNextTokenPair tokenPair) { - return tokenPair.NextToken.LeadingTrivia; - } + if (tokenPair.PreviousToken.RawKind is ((int)SyntaxKind.OpenBraceToken) or + ((int)SyntaxKind.SemicolonToken)) + { + return tokenPair.NextToken.LeadingTrivia; + } - return []; - } + return []; + } - private static IEnumerable AppendTrailingTrivia(PreviousNextTokenPair tokenPair) - { - if (tokenPair.PreviousToken.RawKind is ((int)SyntaxKind.OpenBraceToken) or - ((int)SyntaxKind.SemicolonToken)) + private static IEnumerable AppendTrailingTrivia(PreviousNextTokenPair tokenPair) { - return tokenPair.PreviousToken.TrailingTrivia; - } + if (tokenPair.PreviousToken.RawKind is ((int)SyntaxKind.OpenBraceToken) or + ((int)SyntaxKind.SemicolonToken)) + { + return tokenPair.PreviousToken.TrailingTrivia; + } - return []; + return []; + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs index f98a87b11415a..d9911c8250013 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs @@ -17,189 +17,192 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpMethodExtractor(CSharpSelectionResult result, ExtractMethodGenerationOptions options, bool localFunction) - : MethodExtractor(result, options, localFunction) +internal sealed partial class CSharpExtractMethodService { - protected override CodeGenerator CreateCodeGenerator(AnalyzerResult analyzerResult) - => CSharpCodeGenerator.Create(this.OriginalSelectionResult, analyzerResult, this.Options, this.LocalFunction); - - protected override AnalyzerResult Analyze(CSharpSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken) - => CSharpAnalyzer.Analyze(selectionResult, localFunction, cancellationToken); - - protected override SyntaxNode GetInsertionPointNode( - AnalyzerResult analyzerResult, CancellationToken cancellationToken) + internal sealed partial class CSharpMethodExtractor(CSharpSelectionResult result, ExtractMethodGenerationOptions options, bool localFunction) + : MethodExtractor(result, options, localFunction) { - var originalSpanStart = OriginalSelectionResult.OriginalSpan.Start; - Contract.ThrowIfFalse(originalSpanStart >= 0); + protected override CodeGenerator CreateCodeGenerator(AnalyzerResult analyzerResult) + => CSharpCodeGenerator.Create(this.OriginalSelectionResult, analyzerResult, this.Options, this.LocalFunction); - var document = this.OriginalSelectionResult.SemanticDocument; - var root = document.Root; + protected override AnalyzerResult Analyze(CSharpSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken) + => CSharpAnalyzer.Analyze(selectionResult, localFunction, cancellationToken); - if (LocalFunction) + protected override SyntaxNode GetInsertionPointNode( + AnalyzerResult analyzerResult, CancellationToken cancellationToken) { - // If we are extracting a local function and are within a local function, then we want the new function to be created within the - // existing local function instead of the overarching method. - var outermostCapturedVariable = analyzerResult.GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken); - var baseNode = outermostCapturedVariable != null - ? outermostCapturedVariable.GetIdentifierTokenAtDeclaration(document).Parent - : this.OriginalSelectionResult.GetOutermostCallSiteContainerToProcess(cancellationToken); - - if (baseNode is CompilationUnitSyntax) - { - // In some sort of global statement. Have to special case these a bit for script files. - var globalStatement = root.FindToken(originalSpanStart).GetAncestor(); - if (globalStatement is null) - return null; + var originalSpanStart = OriginalSelectionResult.OriginalSpan.Start; + Contract.ThrowIfFalse(originalSpanStart >= 0); - return GetInsertionPointForGlobalStatement(globalStatement, globalStatement); - } + var document = this.OriginalSelectionResult.SemanticDocument; + var root = document.Root; - var currentNode = baseNode; - while (currentNode is not null) + if (LocalFunction) { - if (currentNode is AnonymousFunctionExpressionSyntax anonymousFunction) + // If we are extracting a local function and are within a local function, then we want the new function to be created within the + // existing local function instead of the overarching method. + var outermostCapturedVariable = analyzerResult.GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken); + var baseNode = outermostCapturedVariable != null + ? outermostCapturedVariable.GetIdentifierTokenAtDeclaration(document).Parent + : this.OriginalSelectionResult.GetOutermostCallSiteContainerToProcess(cancellationToken); + + if (baseNode is CompilationUnitSyntax) { - if (OriginalSelectionWithin(anonymousFunction.Body) || OriginalSelectionWithin(anonymousFunction.ExpressionBody)) - return currentNode; + // In some sort of global statement. Have to special case these a bit for script files. + var globalStatement = root.FindToken(originalSpanStart).GetAncestor(); + if (globalStatement is null) + return null; - if (!OriginalSelectionResult.OriginalSpan.Contains(anonymousFunction.Span)) - { - // If we encountered a function but the selection isn't within the body, it's likely the user - // is attempting to move the function (which is behavior that is supported). Stop looking up the - // tree and assume the encapsulating member is the right place to put the local function. This is to help - // maintain the behavior introduced with https://github.com/dotnet/roslyn/pull/41377 - break; - } + return GetInsertionPointForGlobalStatement(globalStatement, globalStatement); } - if (currentNode is LocalFunctionStatementSyntax localFunction) + var currentNode = baseNode; + while (currentNode is not null) { - if (OriginalSelectionWithin(localFunction.ExpressionBody) || OriginalSelectionWithin(localFunction.Body)) - return currentNode; + if (currentNode is AnonymousFunctionExpressionSyntax anonymousFunction) + { + if (OriginalSelectionWithin(anonymousFunction.Body) || OriginalSelectionWithin(anonymousFunction.ExpressionBody)) + return currentNode; + + if (!OriginalSelectionResult.OriginalSpan.Contains(anonymousFunction.Span)) + { + // If we encountered a function but the selection isn't within the body, it's likely the user + // is attempting to move the function (which is behavior that is supported). Stop looking up the + // tree and assume the encapsulating member is the right place to put the local function. This is to help + // maintain the behavior introduced with https://github.com/dotnet/roslyn/pull/41377 + break; + } + } - if (!OriginalSelectionResult.OriginalSpan.Contains(localFunction.Span)) + if (currentNode is LocalFunctionStatementSyntax localFunction) { - // If we encountered a function but the selection isn't within the body, it's likely the user - // is attempting to move the function (which is behavior that is supported). Stop looking up the - // tree and assume the encapsulating member is the right place to put the local function. This is to help - // maintain the behavior introduced with https://github.com/dotnet/roslyn/pull/41377 - break; + if (OriginalSelectionWithin(localFunction.ExpressionBody) || OriginalSelectionWithin(localFunction.Body)) + return currentNode; + + if (!OriginalSelectionResult.OriginalSpan.Contains(localFunction.Span)) + { + // If we encountered a function but the selection isn't within the body, it's likely the user + // is attempting to move the function (which is behavior that is supported). Stop looking up the + // tree and assume the encapsulating member is the right place to put the local function. This is to help + // maintain the behavior introduced with https://github.com/dotnet/roslyn/pull/41377 + break; + } } - } - if (currentNode is AccessorDeclarationSyntax) - return currentNode; + if (currentNode is AccessorDeclarationSyntax) + return currentNode; - if (currentNode is BaseMethodDeclarationSyntax) - return currentNode; + if (currentNode is BaseMethodDeclarationSyntax) + return currentNode; - if (currentNode is GlobalStatementSyntax globalStatement) - { - // check whether the global statement is a statement container - if (!globalStatement.Statement.IsStatementContainerNode() && !root.SyntaxTree.IsScript()) + if (currentNode is GlobalStatementSyntax globalStatement) { - // The extracted function will be a new global statement - return globalStatement.Parent; + // check whether the global statement is a statement container + if (!globalStatement.Statement.IsStatementContainerNode() && !root.SyntaxTree.IsScript()) + { + // The extracted function will be a new global statement + return globalStatement.Parent; + } + + return globalStatement.Statement; } - return globalStatement.Statement; + currentNode = currentNode.Parent; } - currentNode = currentNode.Parent; + return null; } + else + { + var baseToken = root.FindToken(originalSpanStart); + var primaryConstructorBaseType = baseToken.GetAncestor(); + if (primaryConstructorBaseType != null) + return primaryConstructorBaseType; - return null; - } - else - { - var baseToken = root.FindToken(originalSpanStart); - var primaryConstructorBaseType = baseToken.GetAncestor(); - if (primaryConstructorBaseType != null) - return primaryConstructorBaseType; + var memberNode = baseToken.GetAncestor(); + Contract.ThrowIfNull(memberNode); + Contract.ThrowIfTrue(memberNode.Kind() == SyntaxKind.NamespaceDeclaration); - var memberNode = baseToken.GetAncestor(); - Contract.ThrowIfNull(memberNode); - Contract.ThrowIfTrue(memberNode.Kind() == SyntaxKind.NamespaceDeclaration); + if (memberNode is GlobalStatementSyntax globalStatement) + return GetInsertionPointForGlobalStatement(globalStatement, memberNode); - if (memberNode is GlobalStatementSyntax globalStatement) - return GetInsertionPointForGlobalStatement(globalStatement, memberNode); + return memberNode; + } - return memberNode; - } + SyntaxNode GetInsertionPointForGlobalStatement(GlobalStatementSyntax globalStatement, MemberDeclarationSyntax memberNode) + { + // check whether we are extracting whole global statement out + if (OriginalSelectionResult.FinalSpan.Contains(memberNode.Span)) + return globalStatement.Parent; - SyntaxNode GetInsertionPointForGlobalStatement(GlobalStatementSyntax globalStatement, MemberDeclarationSyntax memberNode) - { - // check whether we are extracting whole global statement out - if (OriginalSelectionResult.FinalSpan.Contains(memberNode.Span)) - return globalStatement.Parent; + // check whether the global statement is a statement container + if (!globalStatement.Statement.IsStatementContainerNode() && !root.SyntaxTree.IsScript()) + { + // The extracted function will be a new global statement + return globalStatement.Parent; + } - // check whether the global statement is a statement container - if (!globalStatement.Statement.IsStatementContainerNode() && !root.SyntaxTree.IsScript()) - { - // The extracted function will be a new global statement - return globalStatement.Parent; + return globalStatement.Statement; } - - return globalStatement.Statement; } - } - private bool OriginalSelectionWithin(SyntaxNode node) - { - if (node is null) + private bool OriginalSelectionWithin(SyntaxNode node) { - return false; - } + if (node is null) + { + return false; + } - return node.Span.Contains(OriginalSelectionResult.OriginalSpan); - } + return node.Span.Contains(OriginalSelectionResult.OriginalSpan); + } - protected override async Task PreserveTriviaAsync(CSharpSelectionResult selectionResult, CancellationToken cancellationToken) - => await CSharpTriviaResult.ProcessAsync(selectionResult, cancellationToken).ConfigureAwait(false); + protected override async Task PreserveTriviaAsync(CSharpSelectionResult selectionResult, CancellationToken cancellationToken) + => await CSharpTriviaResult.ProcessAsync(selectionResult, cancellationToken).ConfigureAwait(false); - protected override Task GenerateCodeAsync(InsertionPoint insertionPoint, CSharpSelectionResult selectionResult, AnalyzerResult analyzeResult, ExtractMethodGenerationOptions options, CancellationToken cancellationToken) - => CSharpCodeGenerator.GenerateAsync(insertionPoint, selectionResult, analyzeResult, options, LocalFunction, cancellationToken); + protected override Task GenerateCodeAsync(InsertionPoint insertionPoint, CSharpSelectionResult selectionResult, AnalyzerResult analyzeResult, ExtractMethodGenerationOptions options, CancellationToken cancellationToken) + => CSharpCodeGenerator.GenerateAsync(insertionPoint, selectionResult, analyzeResult, options, LocalFunction, cancellationToken); - protected override AbstractFormattingRule GetCustomFormattingRule(Document document) - => FormattingRule.Instance; + protected override AbstractFormattingRule GetCustomFormattingRule(Document document) + => FormattingRule.Instance; - protected override SyntaxToken? GetInvocationNameToken(IEnumerable methodNames) - => methodNames.FirstOrNull(t => !t.Parent.IsKind(SyntaxKind.MethodDeclaration)); + protected override SyntaxToken? GetInvocationNameToken(IEnumerable methodNames) + => methodNames.FirstOrNull(t => !t.Parent.IsKind(SyntaxKind.MethodDeclaration)); - protected override SyntaxNode ParseTypeName(string name) - => SyntaxFactory.ParseTypeName(name); + protected override SyntaxNode ParseTypeName(string name) + => SyntaxFactory.ParseTypeName(name); - protected override async Task<(Document document, SyntaxToken? invocationNameToken)> InsertNewLineBeforeLocalFunctionIfNecessaryAsync( - Document document, - SyntaxToken? invocationNameToken, - SyntaxNode methodDefinition, - CancellationToken cancellationToken) - { - // Checking to see if there is already an empty line before the local method declaration. - var leadingTrivia = methodDefinition.GetLeadingTrivia(); - if (!leadingTrivia.Any(t => t.IsKind(SyntaxKind.EndOfLineTrivia) || t.GetStructure() is EndIfDirectiveTriviaSyntax) && - !methodDefinition.FindTokenOnLeftOfPosition(methodDefinition.SpanStart).IsKind(SyntaxKind.OpenBraceToken)) + protected override async Task<(Document document, SyntaxToken? invocationNameToken)> InsertNewLineBeforeLocalFunctionIfNecessaryAsync( + Document document, + SyntaxToken? invocationNameToken, + SyntaxNode methodDefinition, + CancellationToken cancellationToken) { - var originalMethodDefinition = methodDefinition; - var newLine = Options.LineFormattingOptions.NewLine; - methodDefinition = methodDefinition.WithPrependedLeadingTrivia(SyntaxFactory.EndOfLine(newLine)); - - if (!originalMethodDefinition.FindTokenOnLeftOfPosition(originalMethodDefinition.SpanStart).TrailingTrivia.Any(SyntaxKind.EndOfLineTrivia)) + // Checking to see if there is already an empty line before the local method declaration. + var leadingTrivia = methodDefinition.GetLeadingTrivia(); + if (!leadingTrivia.Any(t => t.IsKind(SyntaxKind.EndOfLineTrivia) || t.GetStructure() is EndIfDirectiveTriviaSyntax) && + !methodDefinition.FindTokenOnLeftOfPosition(methodDefinition.SpanStart).IsKind(SyntaxKind.OpenBraceToken)) { - // Add a second new line since there were no line endings in the original form + var originalMethodDefinition = methodDefinition; + var newLine = Options.LineFormattingOptions.NewLine; methodDefinition = methodDefinition.WithPrependedLeadingTrivia(SyntaxFactory.EndOfLine(newLine)); - } - // Generating the new document and associated variables. - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - document = document.WithSyntaxRoot(root.ReplaceNode(originalMethodDefinition, methodDefinition)); + if (!originalMethodDefinition.FindTokenOnLeftOfPosition(originalMethodDefinition.SpanStart).TrailingTrivia.Any(SyntaxKind.EndOfLineTrivia)) + { + // Add a second new line since there were no line endings in the original form + methodDefinition = methodDefinition.WithPrependedLeadingTrivia(SyntaxFactory.EndOfLine(newLine)); + } + + // Generating the new document and associated variables. + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + document = document.WithSyntaxRoot(root.ReplaceNode(originalMethodDefinition, methodDefinition)); - var newRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - if (invocationNameToken != null) - invocationNameToken = newRoot.FindToken(invocationNameToken.Value.SpanStart); - } + if (invocationNameToken != null) + invocationNameToken = newRoot.FindToken(invocationNameToken.Value.SpanStart); + } - return (document, invocationNameToken); + return (document, invocationNameToken); + } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.ExpressionResult.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.ExpressionResult.cs index b6d495704fbaf..14b0509db1a1a 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.ExpressionResult.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.ExpressionResult.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.ExtractMethod; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -13,131 +14,135 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal abstract partial class CSharpSelectionResult +internal sealed partial class CSharpExtractMethodService { - private sealed class ExpressionResult( - TextSpan originalSpan, - TextSpan finalSpan, - bool selectionInExpression, - SemanticDocument document, - SyntaxAnnotation firstTokenAnnotation, - SyntaxAnnotation lastTokenAnnotation, - bool selectionChanged) : CSharpSelectionResult( - originalSpan, finalSpan, selectionInExpression, document, firstTokenAnnotation, lastTokenAnnotation, selectionChanged) + internal abstract partial class CSharpSelectionResult { - public override bool ContainingScopeHasAsyncKeyword() - => false; - - public override SyntaxNode? GetContainingScope() + /// + /// Used when selecting just an expression to extract. + /// + private sealed class ExpressionResult( + SemanticDocument document, + SelectionType selectionType, + TextSpan originalSpan, + TextSpan finalSpan, + bool selectionChanged) : CSharpSelectionResult( + document, selectionType, originalSpan, finalSpan, selectionChanged) { - Contract.ThrowIfNull(SemanticDocument); - Contract.ThrowIfFalse(SelectionInExpression); - - var firstToken = GetFirstTokenInSelection(); - var lastToken = GetLastTokenInSelection(); - var scope = firstToken.GetCommonRoot(lastToken).GetAncestorOrThis(); - if (scope == null) - return null; - - return CSharpSyntaxFacts.Instance.GetRootStandaloneExpression(scope); - } + public override bool ContainingScopeHasAsyncKeyword() + => false; - public override (ITypeSymbol? returnType, bool returnsByRef) GetReturnType() - { - if (GetContainingScope() is not ExpressionSyntax node) + public override SyntaxNode GetContainingScope() { - throw ExceptionUtilities.Unreachable(); - } + Contract.ThrowIfNull(SemanticDocument); + Contract.ThrowIfFalse(IsExtractMethodOnExpression); - var model = SemanticDocument.SemanticModel; + var firstToken = GetFirstTokenInSelection(); + var lastToken = GetLastTokenInSelection(); - // special case for array initializer and explicit cast - if (node.IsArrayInitializer()) - { - var variableDeclExpression = node.GetAncestorOrThis(); - if (variableDeclExpression != null) - return (model.GetTypeInfo(variableDeclExpression.Type).Type, returnsByRef: false); - } + var scope = firstToken.GetCommonRoot(lastToken).GetAncestorOrThis(); + Contract.ThrowIfNull(scope); - if (node.IsExpressionInCast()) - { - // bug # 12774 and # 4780 - // if the expression is under cast, we use the heuristic below - // 1. if regular binding returns a meaningful type, we use it as it is - // 2. if it doesn't, even if the cast itself wasn't included in the selection, we will treat it - // as it was in the selection - var (regularType, returnsByRef) = GetRegularExpressionType(model, node); - if (regularType != null) - return (regularType, returnsByRef); - - if (node.Parent is CastExpressionSyntax castExpression) - return (model.GetTypeInfo(castExpression).Type, returnsByRef: false); + return CSharpSyntaxFacts.Instance.GetRootStandaloneExpression(scope); } - return GetRegularExpressionType(model, node); - } - - private static (ITypeSymbol? typeSymbol, bool returnsByRef) GetRegularExpressionType(SemanticModel semanticModel, ExpressionSyntax node) - { - // regular case. always use ConvertedType to get implicit conversion right. - var expression = node.WalkDownParentheses(); - var returnsByRef = false; - if (expression is RefExpressionSyntax refExpression) + public override (ITypeSymbol? returnType, bool returnsByRef) GetReturnType() { - expression = refExpression.Expression; - returnsByRef = true; - } - - var typeSymbol = GetRegularExpressionTypeWorker(); - return (typeSymbol, returnsByRef); + if (GetContainingScope() is not ExpressionSyntax node) + { + throw ExceptionUtilities.Unreachable(); + } - ITypeSymbol? GetRegularExpressionTypeWorker() - { - var info = semanticModel.GetTypeInfo(expression); - var conv = semanticModel.GetConversion(expression); + var model = SemanticDocument.SemanticModel; - if (info.ConvertedType == null || info.ConvertedType.IsErrorType()) + // special case for array initializer and explicit cast + if (node.IsArrayInitializer()) { - // there is no implicit conversion involved. no need to go further - return info.GetTypeWithAnnotatedNullability(); + var variableDeclExpression = node.GetAncestorOrThis(); + if (variableDeclExpression != null) + return (model.GetTypeInfo(variableDeclExpression.Type).Type, returnsByRef: false); } - // always use converted type if method group - if ((!node.IsKind(SyntaxKind.ObjectCreationExpression) && semanticModel.GetMemberGroup(expression).Length > 0) || - IsCoClassImplicitConversion(info, conv, semanticModel.Compilation.CoClassType())) + if (node.IsExpressionInCast()) { - return info.GetConvertedTypeWithAnnotatedNullability(); + // bug # 12774 and # 4780 + // if the expression is under cast, we use the heuristic below + // 1. if regular binding returns a meaningful type, we use it as it is + // 2. if it doesn't, even if the cast itself wasn't included in the selection, we will treat it + // as it was in the selection + var (regularType, returnsByRef) = GetRegularExpressionType(model, node); + if (regularType != null) + return (regularType, returnsByRef); + + if (node.Parent is CastExpressionSyntax castExpression) + return (model.GetTypeInfo(castExpression).Type, returnsByRef: false); } - // check implicit conversion - if (conv.IsImplicit && (conv.IsConstantExpression || conv.IsEnumeration)) + return GetRegularExpressionType(model, node); + } + + private static (ITypeSymbol? typeSymbol, bool returnsByRef) GetRegularExpressionType(SemanticModel semanticModel, ExpressionSyntax node) + { + // regular case. always use ConvertedType to get implicit conversion right. + var expression = node.WalkDownParentheses(); + var returnsByRef = false; + if (expression is RefExpressionSyntax refExpression) { - return info.GetConvertedTypeWithAnnotatedNullability(); + expression = refExpression.Expression; + returnsByRef = true; } - // use FormattableString if conversion between String and FormattableString - if (info.Type?.SpecialType == SpecialType.System_String && - info.ConvertedType?.IsFormattableStringOrIFormattable() == true) + var typeSymbol = GetRegularExpressionTypeWorker(); + return (typeSymbol, returnsByRef); + + ITypeSymbol? GetRegularExpressionTypeWorker() { - return info.GetConvertedTypeWithAnnotatedNullability(); + var info = semanticModel.GetTypeInfo(expression); + var conv = semanticModel.GetConversion(expression); + + if (info.ConvertedType == null || info.ConvertedType.IsErrorType()) + { + // there is no implicit conversion involved. no need to go further + return info.GetTypeWithAnnotatedNullability(); + } + + // always use converted type if method group + if ((!node.IsKind(SyntaxKind.ObjectCreationExpression) && semanticModel.GetMemberGroup(expression).Length > 0) || + IsCoClassImplicitConversion(info, conv, semanticModel.Compilation.CoClassType())) + { + return info.GetConvertedTypeWithAnnotatedNullability(); + } + + // check implicit conversion + if (conv.IsImplicit && (conv.IsConstantExpression || conv.IsEnumeration)) + { + return info.GetConvertedTypeWithAnnotatedNullability(); + } + + // use FormattableString if conversion between String and FormattableString + if (info.Type?.SpecialType == SpecialType.System_String && + info.ConvertedType?.IsFormattableStringOrIFormattable() == true) + { + return info.GetConvertedTypeWithAnnotatedNullability(); + } + + // always try to use type that is more specific than object type if possible. + return !info.Type.IsObjectType() ? info.GetTypeWithAnnotatedNullability() : info.GetConvertedTypeWithAnnotatedNullability(); } - - // always try to use type that is more specific than object type if possible. - return !info.Type.IsObjectType() ? info.GetTypeWithAnnotatedNullability() : info.GetConvertedTypeWithAnnotatedNullability(); } } - } - private static bool IsCoClassImplicitConversion(TypeInfo info, Conversion conversion, INamedTypeSymbol? coclassSymbol) - { - if (!conversion.IsImplicit || - info.ConvertedType == null || - info.ConvertedType.TypeKind != TypeKind.Interface) + private static bool IsCoClassImplicitConversion(TypeInfo info, Conversion conversion, INamedTypeSymbol? coclassSymbol) { - return false; - } + if (!conversion.IsImplicit || + info.ConvertedType == null || + info.ConvertedType.TypeKind != TypeKind.Interface) + { + return false; + } - // let's see whether this interface has coclass attribute - return info.ConvertedType.HasAttribute(coclassSymbol); + // let's see whether this interface has coclass attribute + return info.ConvertedType.HasAttribute(coclassSymbol); + } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.StatementResult.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.StatementResult.cs index 17b5a01d98628..e3ee8fbdd40ab 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.StatementResult.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.StatementResult.cs @@ -6,96 +6,99 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.ExtractMethod; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal abstract partial class CSharpSelectionResult +internal sealed partial class CSharpExtractMethodService { - private sealed class StatementResult( - TextSpan originalSpan, - TextSpan finalSpan, - bool selectionInExpression, - SemanticDocument document, - SyntaxAnnotation firstTokenAnnotation, - SyntaxAnnotation lastTokenAnnotation, - bool selectionChanged) - : CSharpSelectionResult(originalSpan, finalSpan, selectionInExpression, document, firstTokenAnnotation, lastTokenAnnotation, selectionChanged) + internal abstract partial class CSharpSelectionResult { - public override bool ContainingScopeHasAsyncKeyword() + /// + /// Used when extracting either a single statement, or multiple statements to extract. + /// + private sealed class StatementResult( + SemanticDocument document, + SelectionType selectionType, + TextSpan originalSpan, + TextSpan finalSpan, + bool selectionChanged) + : CSharpSelectionResult(document, selectionType, originalSpan, finalSpan, selectionChanged) { - var node = GetContainingScope(); - - return node switch + public override bool ContainingScopeHasAsyncKeyword() { - AccessorDeclarationSyntax _ => false, - MethodDeclarationSyntax method => method.Modifiers.Any(SyntaxKind.AsyncKeyword), - ParenthesizedLambdaExpressionSyntax lambda => lambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword, - SimpleLambdaExpressionSyntax lambda => lambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword, - AnonymousMethodExpressionSyntax anonymous => anonymous.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword, - _ => false, - }; - } + var node = GetContainingScope(); - public override SyntaxNode GetContainingScope() - { - Contract.ThrowIfNull(SemanticDocument); - Contract.ThrowIfTrue(SelectionInExpression); + return node switch + { + AccessorDeclarationSyntax _ => false, + MethodDeclarationSyntax method => method.Modifiers.Any(SyntaxKind.AsyncKeyword), + ParenthesizedLambdaExpressionSyntax lambda => lambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword, + SimpleLambdaExpressionSyntax lambda => lambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword, + AnonymousMethodExpressionSyntax anonymous => anonymous.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword, + _ => false, + }; + } - // it contains statements - var firstToken = GetFirstTokenInSelection(); - return firstToken.GetAncestors().FirstOrDefault(n => + public override SyntaxNode GetContainingScope() { - return n is AccessorDeclarationSyntax or - LocalFunctionStatementSyntax or - BaseMethodDeclarationSyntax or - AccessorDeclarationSyntax or - ParenthesizedLambdaExpressionSyntax or - SimpleLambdaExpressionSyntax or - AnonymousMethodExpressionSyntax or - CompilationUnitSyntax; - }); - } - - public override (ITypeSymbol returnType, bool returnsByRef) GetReturnType() - { - Contract.ThrowIfTrue(SelectionInExpression); + Contract.ThrowIfNull(SemanticDocument); + Contract.ThrowIfTrue(IsExtractMethodOnExpression); - var node = GetContainingScope(); - var semanticModel = SemanticDocument.SemanticModel; + // it contains statements + var firstToken = GetFirstTokenInSelection(); + return firstToken.GetAncestors().FirstOrDefault(n => + { + return n is AccessorDeclarationSyntax or + LocalFunctionStatementSyntax or + BaseMethodDeclarationSyntax or + AccessorDeclarationSyntax or + AnonymousFunctionExpressionSyntax or + CompilationUnitSyntax; + }); + } - switch (node) + public override (ITypeSymbol returnType, bool returnsByRef) GetReturnType() { - case AccessorDeclarationSyntax access: - // property or event case - if (access.Parent == null || access.Parent.Parent == null) - return default; + Contract.ThrowIfTrue(IsExtractMethodOnExpression); - return semanticModel.GetDeclaredSymbol(access.Parent.Parent) switch - { - IPropertySymbol propertySymbol => (propertySymbol.Type, propertySymbol.ReturnsByRef), - IEventSymbol eventSymbol => (eventSymbol.Type, false), - _ => default, - }; + var node = GetContainingScope(); + var semanticModel = SemanticDocument.SemanticModel; - case MethodDeclarationSyntax methodDeclaration: - { - return semanticModel.GetDeclaredSymbol(methodDeclaration) is not IMethodSymbol method - ? default - : (method.ReturnType, method.ReturnsByRef); - } + switch (node) + { + case AccessorDeclarationSyntax access: + // property or event case + if (access.Parent == null || access.Parent.Parent == null) + return default; - case AnonymousFunctionExpressionSyntax function: - { - return semanticModel.GetSymbolInfo(function).Symbol is not IMethodSymbol method - ? default - : (method.ReturnType, method.ReturnsByRef); - } + return semanticModel.GetDeclaredSymbol(access.Parent.Parent) switch + { + IPropertySymbol propertySymbol => (propertySymbol.Type, propertySymbol.ReturnsByRef), + IEventSymbol eventSymbol => (eventSymbol.Type, false), + _ => default, + }; - default: - return default; + case MethodDeclarationSyntax methodDeclaration: + { + return semanticModel.GetDeclaredSymbol(methodDeclaration) is not IMethodSymbol method + ? default + : (method.ReturnType, method.ReturnsByRef); + } + + case AnonymousFunctionExpressionSyntax function: + { + return semanticModel.GetSymbolInfo(function).Symbol is not IMethodSymbol method + ? default + : (method.ReturnType, method.ReturnsByRef); + } + + default: + return default; + } } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.cs index 5ae8ce2baf5a7..afd32d2b6a423 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.cs @@ -19,220 +19,205 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal abstract partial class CSharpSelectionResult( - TextSpan originalSpan, - TextSpan finalSpan, - bool selectionInExpression, - SemanticDocument document, - SyntaxAnnotation firstTokenAnnotation, - SyntaxAnnotation lastTokenAnnotation, - bool selectionChanged) - : SelectionResult( - originalSpan, finalSpan, selectionInExpression, document, firstTokenAnnotation, lastTokenAnnotation, selectionChanged) +internal sealed partial class CSharpExtractMethodService { - public static async Task CreateAsync( + internal abstract partial class CSharpSelectionResult( + SemanticDocument document, + SelectionType selectionType, TextSpan originalSpan, TextSpan finalSpan, - bool selectionInExpression, - SemanticDocument document, - SyntaxToken firstToken, - SyntaxToken lastToken, - bool selectionChanged, - CancellationToken cancellationToken) + bool selectionChanged) + : SelectionResult( + document, selectionType, originalSpan, finalSpan, selectionChanged) { - Contract.ThrowIfNull(document); - - var firstTokenAnnotation = new SyntaxAnnotation(); - var lastTokenAnnotation = new SyntaxAnnotation(); - - var root = await document.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var newDocument = await SemanticDocument.CreateAsync(document.Document.WithSyntaxRoot(AddAnnotations( - root, - [ - (firstToken, firstTokenAnnotation), - (lastToken, lastTokenAnnotation) - ])), cancellationToken).ConfigureAwait(false); - - if (selectionInExpression) + public static async Task CreateAsync( + SemanticDocument document, + SelectionInfo selectionInfo, + bool selectionChanged, + CancellationToken cancellationToken) { - return new ExpressionResult( - originalSpan, finalSpan, selectionInExpression, - newDocument, firstTokenAnnotation, lastTokenAnnotation, selectionChanged); + Contract.ThrowIfNull(document); + + var root = await document.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newDocument = await SemanticDocument.CreateAsync(document.Document.WithSyntaxRoot(AddAnnotations( + root, + [ + (selectionInfo.FirstTokenInFinalSpan, s_firstTokenAnnotation), + (selectionInfo.LastTokenInFinalSpan, s_lastTokenAnnotation) + ])), cancellationToken).ConfigureAwait(false); + + var selectionType = selectionInfo.GetSelectionType(); + var originalSpan = selectionInfo.OriginalSpan; + var finalSpan = selectionInfo.FinalSpan; + + return selectionType == SelectionType.Expression + ? new ExpressionResult(newDocument, selectionType, originalSpan, finalSpan, selectionChanged) + : new StatementResult(newDocument, selectionType, originalSpan, finalSpan, selectionChanged); } - else - { - return new StatementResult( - originalSpan, finalSpan, selectionInExpression, - newDocument, firstTokenAnnotation, lastTokenAnnotation, selectionChanged); - } - } - protected override ISyntaxFacts SyntaxFacts - => CSharpSyntaxFacts.Instance; + protected override ISyntaxFacts SyntaxFacts + => CSharpSyntaxFacts.Instance; - public override SyntaxNode GetNodeForDataFlowAnalysis() - { - var node = base.GetNodeForDataFlowAnalysis(); + public override SyntaxNode GetNodeForDataFlowAnalysis() + { + var node = base.GetNodeForDataFlowAnalysis(); - // If we're returning a value by ref we actually want to do the analysis on the underlying expression. - return node is RefExpressionSyntax refExpression - ? refExpression.Expression - : node; - } + // If we're returning a value by ref we actually want to do the analysis on the underlying expression. + return node is RefExpressionSyntax refExpression + ? refExpression.Expression + : node; + } - protected override bool UnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken) - => IsUnderAnonymousOrLocalMethod(token, firstToken, lastToken); + protected override bool UnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken) + => IsUnderAnonymousOrLocalMethod(token, firstToken, lastToken); - public static bool IsUnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken) - { - for (var current = token.Parent; current != null; current = current.Parent) + public static bool IsUnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken) { - if (current is MemberDeclarationSyntax) - return false; - - if (current is AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax) + for (var current = token.Parent; current != null; current = current.Parent) { - // make sure the selection contains the lambda - return firstToken.SpanStart <= current.GetFirstToken().SpanStart && - current.GetLastToken().Span.End <= lastToken.Span.End; + if (current is MemberDeclarationSyntax) + return false; + + if (current is AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax) + { + // make sure the selection contains the lambda + return firstToken.SpanStart <= current.GetFirstToken().SpanStart && + current.GetLastToken().Span.End <= lastToken.Span.End; + } } - } - return false; - } + return false; + } - public override SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken) - { - if (this.SelectionInExpression) + public override SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken) { - var container = this.GetInnermostStatementContainer(); + if (this.IsExtractMethodOnExpression) + { + var container = this.GetInnermostStatementContainer(); - Contract.ThrowIfNull(container); - Contract.ThrowIfFalse( - container.IsStatementContainerNode() || - container is BaseListSyntax or TypeDeclarationSyntax or ConstructorDeclarationSyntax or CompilationUnitSyntax); + Contract.ThrowIfNull(container); + Contract.ThrowIfFalse( + container.IsStatementContainerNode() || + container is BaseListSyntax or TypeDeclarationSyntax or ConstructorDeclarationSyntax or CompilationUnitSyntax); - return container; - } + return container; + } - if (this.IsExtractMethodOnSingleStatement()) - { - var firstStatement = this.GetFirstStatement(); - return firstStatement.Parent; + if (this.IsExtractMethodOnSingleStatement) + { + var firstStatement = this.GetFirstStatement(); + return firstStatement.Parent; + } + + if (this.IsExtractMethodOnMultipleStatements) + { + var firstStatement = this.GetFirstStatementUnderContainer(); + var container = firstStatement.Parent; + if (container is GlobalStatementSyntax) + return container.Parent; + + return container; + } + + throw ExceptionUtilities.Unreachable(); } - if (this.IsExtractMethodOnMultipleStatements()) + public override StatementSyntax GetFirstStatementUnderContainer() { - var firstStatement = this.GetFirstStatementUnderContainer(); - var container = firstStatement.Parent; - if (container is GlobalStatementSyntax) - return container.Parent; + Contract.ThrowIfTrue(IsExtractMethodOnExpression); - return container; + var firstToken = GetFirstTokenInSelection(); + var statement = firstToken.Parent.GetStatementUnderContainer(); + Contract.ThrowIfNull(statement); + + return statement; } - throw ExceptionUtilities.Unreachable(); - } + public override StatementSyntax GetLastStatementUnderContainer() + { + Contract.ThrowIfTrue(IsExtractMethodOnExpression); - public override StatementSyntax GetFirstStatementUnderContainer() - { - Contract.ThrowIfTrue(SelectionInExpression); + var lastToken = GetLastTokenInSelection(); + var statement = lastToken.Parent.GetStatementUnderContainer(); - var firstToken = GetFirstTokenInSelection(); - var statement = firstToken.Parent.GetStatementUnderContainer(); - Contract.ThrowIfNull(statement); + return statement; + } - return statement; - } + public SyntaxNode GetInnermostStatementContainer() + { + Contract.ThrowIfFalse(IsExtractMethodOnExpression); + var containingScope = GetContainingScope(); + var statements = containingScope.GetAncestorsOrThis(); + StatementSyntax last = null; - public override StatementSyntax GetLastStatementUnderContainer() - { - Contract.ThrowIfTrue(SelectionInExpression); + foreach (var statement in statements) + { + if (statement.IsStatementContainerNode()) + return statement; - var lastToken = GetLastTokenInSelection(); - var statement = lastToken.Parent.GetStatementUnderContainer(); + last = statement; + } - Contract.ThrowIfNull(statement); - var firstStatementUnderContainer = GetFirstStatementUnderContainer(); - Contract.ThrowIfFalse(CSharpSyntaxFacts.Instance.AreStatementsInSameContainer(statement, firstStatementUnderContainer)); + // expression bodied member case + var expressionBodiedMember = GetContainingScopeOf(); + if (expressionBodiedMember != null) + { + // the class/struct declaration is the innermost statement container, since the + // member does not have a block body + return GetContainingScopeOf(); + } - return statement; - } + // constructor initializer case + var constructorInitializer = GetContainingScopeOf(); + if (constructorInitializer != null) + return constructorInitializer.Parent; - public SyntaxNode GetInnermostStatementContainer() - { - Contract.ThrowIfFalse(SelectionInExpression); - var containingScope = GetContainingScope(); - var statements = containingScope.GetAncestorsOrThis(); - StatementSyntax last = null; + // field initializer case + var field = GetContainingScopeOf(); + if (field != null) + return field.Parent; - foreach (var statement in statements) - { - if (statement.IsStatementContainerNode()) - return statement; + var primaryConstructorBaseType = GetContainingScopeOf(); + if (primaryConstructorBaseType != null) + return primaryConstructorBaseType.Parent; - last = statement; + Contract.ThrowIfFalse(last.IsParentKind(SyntaxKind.GlobalStatement)); + Contract.ThrowIfFalse(last.Parent.IsParentKind(SyntaxKind.CompilationUnit)); + return last.Parent.Parent; } - // expression bodied member case - var expressionBodiedMember = GetContainingScopeOf(); - if (expressionBodiedMember != null) + public bool ShouldPutUnsafeModifier() { - // the class/struct declaration is the innermost statement container, since the - // member does not have a block body - return GetContainingScopeOf(); - } - - // constructor initializer case - var constructorInitializer = GetContainingScopeOf(); - if (constructorInitializer != null) - return constructorInitializer.Parent; - - // field initializer case - var field = GetContainingScopeOf(); - if (field != null) - return field.Parent; + var token = GetFirstTokenInSelection(); + var ancestors = token.GetAncestors(); - var primaryConstructorBaseType = GetContainingScopeOf(); - if (primaryConstructorBaseType != null) - return primaryConstructorBaseType.Parent; - - Contract.ThrowIfFalse(last.IsParentKind(SyntaxKind.GlobalStatement)); - Contract.ThrowIfFalse(last.Parent.IsParentKind(SyntaxKind.CompilationUnit)); - return last.Parent.Parent; - } - - public bool ShouldPutUnsafeModifier() - { - var token = GetFirstTokenInSelection(); - var ancestors = token.GetAncestors(); + // if enclosing type contains unsafe keyword, we don't need to put it again + if (ancestors.Where(a => CSharp.SyntaxFacts.IsTypeDeclaration(a.Kind())) + .Cast() + .Any(m => m.GetModifiers().Any(SyntaxKind.UnsafeKeyword))) + { + return false; + } - // if enclosing type contains unsafe keyword, we don't need to put it again - if (ancestors.Where(a => CSharp.SyntaxFacts.IsTypeDeclaration(a.Kind())) - .Cast() - .Any(m => m.GetModifiers().Any(SyntaxKind.UnsafeKeyword))) - { - return false; + return token.Parent.IsUnsafeContext(); } - return token.Parent.IsUnsafeContext(); - } - - public SyntaxKind UnderCheckedExpressionContext() - => UnderCheckedContext(); + public SyntaxKind UnderCheckedExpressionContext() + => UnderCheckedContext(); - public SyntaxKind UnderCheckedStatementContext() - => UnderCheckedContext(); + public SyntaxKind UnderCheckedStatementContext() + => UnderCheckedContext(); - private SyntaxKind UnderCheckedContext() where T : SyntaxNode - { - var token = GetFirstTokenInSelection(); - var contextNode = token.Parent.GetAncestor(); - if (contextNode == null) + private SyntaxKind UnderCheckedContext() where T : SyntaxNode { - return SyntaxKind.None; - } + var token = GetFirstTokenInSelection(); + var contextNode = token.Parent.GetAncestor(); + if (contextNode == null) + { + return SyntaxKind.None; + } - return contextNode.Kind(); + return contextNode.Kind(); + } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.Validator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.Validator.cs index 705e4a053739e..f7281218f1ee6 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.Validator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.Validator.cs @@ -10,65 +10,68 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpSelectionValidator +internal sealed partial class CSharpExtractMethodService { - public static bool Check(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) - => node switch + internal sealed partial class CSharpSelectionValidator + { + public static bool Check(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) + => node switch + { + ExpressionSyntax expression => CheckExpression(semanticModel, expression, cancellationToken), + BlockSyntax block => CheckBlock(block), + StatementSyntax statement => CheckStatement(statement), + GlobalStatementSyntax _ => CheckGlobalStatement(), + _ => false, + }; + + private static bool CheckGlobalStatement() + => true; + + private static bool CheckBlock(BlockSyntax block) { - ExpressionSyntax expression => CheckExpression(semanticModel, expression, cancellationToken), - BlockSyntax block => CheckBlock(block), - StatementSyntax statement => CheckStatement(statement), - GlobalStatementSyntax _ => CheckGlobalStatement(), - _ => false, - }; + // TODO(cyrusn): Is it intentional that fixed statement is not in this list? + return block.Parent is BlockSyntax or + DoStatementSyntax or + ElseClauseSyntax or + CommonForEachStatementSyntax or + ForStatementSyntax or + IfStatementSyntax or + LockStatementSyntax or + UsingStatementSyntax or + WhileStatementSyntax; + } - private static bool CheckGlobalStatement() - => true; + private static bool CheckExpression(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - private static bool CheckBlock(BlockSyntax block) - { - // TODO(cyrusn): Is it intentional that fixed statement is not in this list? - return block.Parent is BlockSyntax or + // TODO(cyrusn): This is probably unnecessary. What we should be doing is binding + // the type of the expression and seeing if it contains an anonymous type. + if (expression is AnonymousObjectCreationExpressionSyntax) + { + return false; + } + + return expression.CanReplaceWithRValue(semanticModel, cancellationToken); + } + + private static bool CheckStatement(StatementSyntax statement) + => statement is CheckedStatementSyntax or DoStatementSyntax or - ElseClauseSyntax or + EmptyStatementSyntax or + ExpressionStatementSyntax or + FixedStatementSyntax or CommonForEachStatementSyntax or ForStatementSyntax or IfStatementSyntax or + LocalDeclarationStatementSyntax or LockStatementSyntax or + ReturnStatementSyntax or + SwitchStatementSyntax or + ThrowStatementSyntax or + TryStatementSyntax or + UnsafeStatementSyntax or UsingStatementSyntax or WhileStatementSyntax; } - - private static bool CheckExpression(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - // TODO(cyrusn): This is probably unnecessary. What we should be doing is binding - // the type of the expression and seeing if it contains an anonymous type. - if (expression is AnonymousObjectCreationExpressionSyntax) - { - return false; - } - - return expression.CanReplaceWithRValue(semanticModel, cancellationToken); - } - - private static bool CheckStatement(StatementSyntax statement) - => statement is CheckedStatementSyntax or - DoStatementSyntax or - EmptyStatementSyntax or - ExpressionStatementSyntax or - FixedStatementSyntax or - CommonForEachStatementSyntax or - ForStatementSyntax or - IfStatementSyntax or - LocalDeclarationStatementSyntax or - LockStatementSyntax or - ReturnStatementSyntax or - SwitchStatementSyntax or - ThrowStatementSyntax or - TryStatementSyntax or - UnsafeStatementSyntax or - UsingStatementSyntax or - WhileStatementSyntax; } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.cs index e622de3cc0e01..9990d231f06bc 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.cs @@ -2,16 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.ExtractMethod; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -20,538 +18,574 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; -internal sealed partial class CSharpSelectionValidator( - SemanticDocument document, - TextSpan textSpan, - bool localFunction) : SelectionValidator(document, textSpan) +internal sealed partial class CSharpExtractMethodService { - private readonly bool _localFunction = localFunction; - - public override async Task<(CSharpSelectionResult, OperationStatus)> GetValidSelectionAsync(CancellationToken cancellationToken) + internal sealed partial class CSharpSelectionValidator( + SemanticDocument document, + TextSpan textSpan, + bool localFunction) : SelectionValidator(document, textSpan) { - if (!ContainsValidSelection) - return (null, OperationStatus.FailedWithUnknownReason); - - var text = SemanticDocument.Text; - var root = SemanticDocument.Root; - var model = SemanticDocument.SemanticModel; - var doc = SemanticDocument; - - // go through pipe line and calculate information about the user selection - var selectionInfo = GetInitialSelectionInfo(root, text); - selectionInfo = AssignInitialFinalTokens(selectionInfo, root, cancellationToken); - selectionInfo = AdjustFinalTokensBasedOnContext(selectionInfo, model, cancellationToken); - selectionInfo = AssignFinalSpan(selectionInfo, text); - selectionInfo = ApplySpecialCases(selectionInfo, text, SemanticDocument.SyntaxTree.Options, _localFunction); - selectionInfo = CheckErrorCasesAndAppendDescriptions(selectionInfo, root); - - // there was a fatal error that we couldn't even do negative preview, return error result - if (selectionInfo.Status.Failed) - return (null, selectionInfo.Status); - - var controlFlowSpan = GetControlFlowSpan(selectionInfo); - if (!selectionInfo.SelectionInExpression) + private readonly bool _localFunction = localFunction; + + protected override bool AreStatementsInSameContainer(StatementSyntax statement1, StatementSyntax statement2) { - var statementRange = GetStatementRangeContainedInSpan(root, controlFlowSpan, cancellationToken); - if (statementRange == null) - { - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.Cannot_determine_valid_range_of_statements_to_extract)); - return (null, selectionInfo.Status); - } + if (statement1.Parent == statement2.Parent) + return true; - var isFinalSpanSemanticallyValid = IsFinalSpanSemanticallyValidSpan(model, controlFlowSpan, statementRange.Value, cancellationToken); - if (!isFinalSpanSemanticallyValid) + if (statement1.Parent is GlobalStatementSyntax + && statement2.Parent is GlobalStatementSyntax + && statement1.Parent.Parent == statement2.Parent.Parent) { - // check control flow only if we are extracting statement level, not expression - // level. you can not have goto that moves control out of scope in expression level - // (even in lambda) - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: true, FeaturesResources.Not_all_code_paths_return)); + return true; } + + return false; } - var selectionChanged = selectionInfo.FirstTokenInOriginalSpan != selectionInfo.FirstTokenInFinalSpan || selectionInfo.LastTokenInOriginalSpan != selectionInfo.LastTokenInFinalSpan; - - var result = await CSharpSelectionResult.CreateAsync( - selectionInfo.OriginalSpan, - selectionInfo.FinalSpan, - selectionInfo.SelectionInExpression, - doc, - selectionInfo.FirstTokenInFinalSpan, - selectionInfo.LastTokenInFinalSpan, - selectionChanged, - cancellationToken).ConfigureAwait(false); - return (result, selectionInfo.Status); - } + protected override SelectionInfo GetInitialSelectionInfo(CancellationToken cancellationToken) + { + var text = SemanticDocument.Text; + var root = SemanticDocument.Root; + var model = SemanticDocument.SemanticModel; + + // go through pipe line and calculate information about the user selection + var selectionInfo = GetInitialSelectionInfo(root, text); + selectionInfo = AssignInitialFinalTokens(selectionInfo, root, cancellationToken); + selectionInfo = AdjustFinalTokensBasedOnContext(selectionInfo, model, cancellationToken); + selectionInfo = AssignFinalSpan(selectionInfo, text); + selectionInfo = ApplySpecialCases(selectionInfo, text, SemanticDocument.SyntaxTree.Options, _localFunction); + selectionInfo = CheckErrorCasesAndAppendDescriptions(selectionInfo, root); - private SelectionInfo ApplySpecialCases(SelectionInfo selectionInfo, SourceText text, ParseOptions options, bool localFunction) - { - if (selectionInfo.Status.Failed) return selectionInfo; + } - // If we're under a global statement (and not inside an inner lambda/local-function) then there are restrictions - // on if we can extract a method vs a local function. - if (IsCodeInGlobalLevel()) + protected override Task CreateSelectionResultAsync( + SelectionInfo selectionInfo, CancellationToken cancellationToken) { - // Cannot extract a method from a top-level statement in normal code - if (!localFunction && options is { Kind: SourceCodeKind.Regular }) - return selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.Selection_cannot_include_top_level_statements)); + Contract.ThrowIfFalse(ContainsValidSelection); + Contract.ThrowIfFalse(selectionInfo.Status.Succeeded); - // Cannot extract a local function from a global statement in script code - if (localFunction && options is { Kind: SourceCodeKind.Script }) - return selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.Selection_cannot_include_global_statements)); + var selectionChanged = selectionInfo.FirstTokenInOriginalSpan != selectionInfo.FirstTokenInFinalSpan || selectionInfo.LastTokenInOriginalSpan != selectionInfo.LastTokenInFinalSpan; + + return CSharpSelectionResult.CreateAsync( + SemanticDocument, + selectionInfo, + selectionChanged, + cancellationToken); } - if (_localFunction) + private SelectionInfo ApplySpecialCases(SelectionInfo selectionInfo, SourceText text, ParseOptions options, bool localFunction) { - foreach (var ancestor in selectionInfo.CommonRootFromOriginalSpan.AncestorsAndSelf()) + if (selectionInfo.Status.Failed) + return selectionInfo; + + // If we're under a global statement (and not inside an inner lambda/local-function) then there are restrictions + // on if we can extract a method vs a local function. + if (IsCodeInGlobalLevel()) { - if (ancestor.Kind() is SyntaxKind.BaseConstructorInitializer or SyntaxKind.ThisConstructorInitializer) - return selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.Selection_cannot_be_in_constructor_initializer)); + // Cannot extract a method from a top-level statement in normal code + if (!localFunction && options is { Kind: SourceCodeKind.Regular }) + return selectionInfo with { Status = selectionInfo.Status.With(succeeded: false, CSharpFeaturesResources.Selection_cannot_include_top_level_statements) }; - if (ancestor is AnonymousFunctionExpressionSyntax) - break; + // Cannot extract a local function from a global statement in script code + if (localFunction && options is { Kind: SourceCodeKind.Script }) + return selectionInfo with { Status = selectionInfo.Status.With(succeeded: false, CSharpFeaturesResources.Selection_cannot_include_global_statements) }; } - } - - if (!selectionInfo.SelectionInExpression) - return selectionInfo; - - var expressionNode = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); - if (expressionNode is not AssignmentExpressionSyntax assign) - return selectionInfo; - - // make sure there is a visible token at right side expression - if (assign.Right.GetLastToken().Kind() == SyntaxKind.None) - return selectionInfo; - - return AssignFinalSpan(selectionInfo - .With(s => s.FirstTokenInFinalSpan = assign.Right.GetFirstToken(includeZeroWidth: true)) - .With(s => s.LastTokenInFinalSpan = assign.Right.GetLastToken(includeZeroWidth: true)), text); - bool IsCodeInGlobalLevel() - { - for (var current = selectionInfo.CommonRootFromOriginalSpan; current != null; current = current.Parent) + if (_localFunction) { - if (current is CompilationUnitSyntax) - return true; - - if (current is GlobalStatementSyntax) - return true; + foreach (var ancestor in selectionInfo.CommonRootFromOriginalSpan.AncestorsAndSelf()) + { + if (ancestor.Kind() is SyntaxKind.BaseConstructorInitializer or SyntaxKind.ThisConstructorInitializer) + return selectionInfo with { Status = selectionInfo.Status.With(succeeded: false, CSharpFeaturesResources.Selection_cannot_be_in_constructor_initializer) }; - if (current is AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax or MemberDeclarationSyntax) - return false; + if (ancestor is AnonymousFunctionExpressionSyntax) + break; + } } - throw ExceptionUtilities.Unreachable(); - } - } + if (!selectionInfo.SelectionInExpression) + return selectionInfo; - private static TextSpan GetControlFlowSpan(SelectionInfo selectionInfo) - => TextSpan.FromBounds(selectionInfo.FirstTokenInFinalSpan.SpanStart, selectionInfo.LastTokenInFinalSpan.Span.End); + var expressionNode = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); + if (expressionNode is not AssignmentExpressionSyntax assign) + return selectionInfo; - private static SelectionInfo AdjustFinalTokensBasedOnContext( - SelectionInfo selectionInfo, - SemanticModel semanticModel, - CancellationToken cancellationToken) - { - if (selectionInfo.Status.Failed) - return selectionInfo; + // make sure there is a visible token at right side expression + if (assign.Right.GetLastToken().Kind() == SyntaxKind.None) + return selectionInfo; - // don't need to adjust anything if it is multi-statements case - if (!selectionInfo.SelectionInExpression && !selectionInfo.SelectionInSingleStatement) - { - return selectionInfo; - } + return AssignFinalSpan(selectionInfo with + { + FirstTokenInFinalSpan = assign.Right.GetFirstToken(includeZeroWidth: true), + LastTokenInFinalSpan = assign.Right.GetLastToken(includeZeroWidth: true), + }, text); + + bool IsCodeInGlobalLevel() + { + for (var current = selectionInfo.CommonRootFromOriginalSpan; current != null; current = current.Parent) + { + if (current is CompilationUnitSyntax) + return true; - // get the node that covers the selection - var node = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); + if (current is GlobalStatementSyntax) + return true; - var validNode = Check(semanticModel, node, cancellationToken); - if (validNode) - { - return selectionInfo; + if (current is AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax or MemberDeclarationSyntax) + return false; + } + + throw ExceptionUtilities.Unreachable(); + } } - var firstValidNode = node.GetAncestors().FirstOrDefault(n => Check(semanticModel, n, cancellationToken)); - if (firstValidNode == null) + private static SelectionInfo AdjustFinalTokensBasedOnContext( + SelectionInfo selectionInfo, + SemanticModel semanticModel, + CancellationToken cancellationToken) { - // couldn't find any valid node - return selectionInfo.WithStatus(s => new OperationStatus(succeeded: false, CSharpFeaturesResources.Selection_does_not_contain_a_valid_node)) - .With(s => s.FirstTokenInFinalSpan = default) - .With(s => s.LastTokenInFinalSpan = default); - } + if (selectionInfo.Status.Failed) + return selectionInfo; - firstValidNode = (firstValidNode.Parent is ExpressionStatementSyntax) ? firstValidNode.Parent : firstValidNode; + // don't need to adjust anything if it is multi-statements case + if (!selectionInfo.SelectionInExpression && !selectionInfo.SelectionInSingleStatement) + { + return selectionInfo; + } - return selectionInfo.With(s => s.SelectionInExpression = firstValidNode is ExpressionSyntax) - .With(s => s.SelectionInSingleStatement = firstValidNode is StatementSyntax) - .With(s => s.FirstTokenInFinalSpan = firstValidNode.GetFirstToken(includeZeroWidth: true)) - .With(s => s.LastTokenInFinalSpan = firstValidNode.GetLastToken(includeZeroWidth: true)); - } + // get the node that covers the selection + var node = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); - private SelectionInfo GetInitialSelectionInfo(SyntaxNode root, SourceText text) - { - var adjustedSpan = GetAdjustedSpan(text, OriginalSpan); + var validNode = Check(semanticModel, node, cancellationToken); + if (validNode) + { + return selectionInfo; + } - var firstTokenInSelection = root.FindTokenOnRightOfPosition(adjustedSpan.Start, includeSkipped: false); - var lastTokenInSelection = root.FindTokenOnLeftOfPosition(adjustedSpan.End, includeSkipped: false); + var firstValidNode = node.GetAncestors().FirstOrDefault(n => Check(semanticModel, n, cancellationToken)); + if (firstValidNode == null) + { + // couldn't find any valid node + return selectionInfo with + { + Status = new(succeeded: false, CSharpFeaturesResources.Selection_does_not_contain_a_valid_node), + FirstTokenInFinalSpan = default, + LastTokenInFinalSpan = default, + }; + } - if (firstTokenInSelection.Kind() == SyntaxKind.None || lastTokenInSelection.Kind() == SyntaxKind.None) - { - return new SelectionInfo { Status = new OperationStatus(succeeded: false, FeaturesResources.Invalid_selection), OriginalSpan = adjustedSpan }; - } + firstValidNode = (firstValidNode.Parent is ExpressionStatementSyntax) ? firstValidNode.Parent : firstValidNode; - if (firstTokenInSelection.SpanStart > lastTokenInSelection.Span.End) - { - return new SelectionInfo + return selectionInfo with { - Status = new OperationStatus(succeeded: false, FeaturesResources.Selection_does_not_contain_a_valid_token), - OriginalSpan = adjustedSpan, - FirstTokenInOriginalSpan = firstTokenInSelection, - LastTokenInOriginalSpan = lastTokenInSelection + SelectionInExpression = firstValidNode is ExpressionSyntax, + SelectionInSingleStatement = firstValidNode is StatementSyntax, + FirstTokenInFinalSpan = firstValidNode.GetFirstToken(includeZeroWidth: true), + LastTokenInFinalSpan = firstValidNode.GetLastToken(includeZeroWidth: true), }; } - if (!UnderValidContext(firstTokenInSelection) || !UnderValidContext(lastTokenInSelection)) + private SelectionInfo GetInitialSelectionInfo(SyntaxNode root, SourceText text) { - return new SelectionInfo + var adjustedSpan = GetAdjustedSpan(text, OriginalSpan); + + var firstTokenInSelection = root.FindTokenOnRightOfPosition(adjustedSpan.Start, includeSkipped: false); + var lastTokenInSelection = root.FindTokenOnLeftOfPosition(adjustedSpan.End, includeSkipped: false); + + if (firstTokenInSelection.Kind() == SyntaxKind.None || lastTokenInSelection.Kind() == SyntaxKind.None) { - Status = new OperationStatus(succeeded: false, FeaturesResources.No_valid_selection_to_perform_extraction), - OriginalSpan = adjustedSpan, - FirstTokenInOriginalSpan = firstTokenInSelection, - LastTokenInOriginalSpan = lastTokenInSelection - }; - } + return new SelectionInfo { Status = new OperationStatus(succeeded: false, FeaturesResources.Invalid_selection), OriginalSpan = adjustedSpan }; + } - var commonRoot = firstTokenInSelection.GetCommonRoot(lastTokenInSelection); + if (firstTokenInSelection.SpanStart > lastTokenInSelection.Span.End) + { + return new() + { + Status = new OperationStatus(succeeded: false, FeaturesResources.Selection_does_not_contain_a_valid_token), + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } - if (commonRoot == null) - { - return new SelectionInfo + if (!UnderValidContext(firstTokenInSelection) || !UnderValidContext(lastTokenInSelection)) { - Status = new OperationStatus(succeeded: false, FeaturesResources.No_common_root_node_for_extraction), - OriginalSpan = adjustedSpan, - FirstTokenInOriginalSpan = firstTokenInSelection, - LastTokenInOriginalSpan = lastTokenInSelection - }; - } + return new() + { + Status = new OperationStatus(succeeded: false, FeaturesResources.No_valid_selection_to_perform_extraction), + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } - if (!commonRoot.ContainedInValidType()) - { - return new SelectionInfo + var commonRoot = firstTokenInSelection.GetCommonRoot(lastTokenInSelection); + + if (commonRoot == null) { - Status = new OperationStatus(succeeded: false, FeaturesResources.Selection_not_contained_inside_a_type), - OriginalSpan = adjustedSpan, - FirstTokenInOriginalSpan = firstTokenInSelection, - LastTokenInOriginalSpan = lastTokenInSelection - }; - } + return new() + { + Status = new OperationStatus(succeeded: false, FeaturesResources.No_common_root_node_for_extraction), + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } - var selectionInExpression = commonRoot is ExpressionSyntax; - if (!selectionInExpression && !commonRoot.UnderValidContext()) - { - return new SelectionInfo + if (!commonRoot.ContainedInValidType()) + { + return new() + { + Status = new OperationStatus(succeeded: false, FeaturesResources.Selection_not_contained_inside_a_type), + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } + + var selectionInExpression = commonRoot is ExpressionSyntax; + if (!selectionInExpression && !commonRoot.UnderValidContext()) { - Status = new OperationStatus(succeeded: false, FeaturesResources.No_valid_selection_to_perform_extraction), + return new() + { + Status = new OperationStatus(succeeded: false, FeaturesResources.No_valid_selection_to_perform_extraction), + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } + + return new() + { + Status = OperationStatus.SucceededStatus, OriginalSpan = adjustedSpan, + CommonRootFromOriginalSpan = commonRoot, + SelectionInExpression = selectionInExpression, FirstTokenInOriginalSpan = firstTokenInSelection, LastTokenInOriginalSpan = lastTokenInSelection }; } - return new SelectionInfo - { - Status = OperationStatus.SucceededStatus, - OriginalSpan = adjustedSpan, - CommonRootFromOriginalSpan = commonRoot, - SelectionInExpression = selectionInExpression, - FirstTokenInOriginalSpan = firstTokenInSelection, - LastTokenInOriginalSpan = lastTokenInSelection - }; - } + private static bool UnderValidContext(SyntaxToken token) + => token.GetAncestors().Any(n => CheckTopLevel(n, token.Span)); - private static bool UnderValidContext(SyntaxToken token) - => token.GetAncestors().Any(n => CheckTopLevel(n, token.Span)); - - private static bool CheckTopLevel(SyntaxNode node, TextSpan span) - { - switch (node) + private static bool CheckTopLevel(SyntaxNode node, TextSpan span) { - case BlockSyntax block: - return ContainsInBlockBody(block, span); + switch (node) + { + case BlockSyntax block: + return ContainsInBlockBody(block, span); - case ArrowExpressionClauseSyntax expressionBodiedMember: - return ContainsInExpressionBodiedMemberBody(expressionBodiedMember, span); + case ArrowExpressionClauseSyntax expressionBodiedMember: + return ContainsInExpressionBodiedMemberBody(expressionBodiedMember, span); - case FieldDeclarationSyntax field: - { - foreach (var variable in field.Declaration.Variables) + case FieldDeclarationSyntax field: { - if (variable.Initializer != null && variable.Initializer.Span.Contains(span)) + foreach (var variable in field.Declaration.Variables) { - return true; + if (variable.Initializer != null && variable.Initializer.Span.Contains(span)) + { + return true; + } } - } - break; - } + break; + } - case GlobalStatementSyntax: - return true; + case GlobalStatementSyntax: + return true; - case ConstructorInitializerSyntax constructorInitializer: - return constructorInitializer.ContainsInArgument(span); + case ConstructorInitializerSyntax constructorInitializer: + return constructorInitializer.ContainsInArgument(span); - case PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType: - return primaryConstructorBaseType.ArgumentList.Arguments.FullSpan.Contains(span); - } - - return false; - } + case PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType: + return primaryConstructorBaseType.ArgumentList.Arguments.FullSpan.Contains(span); + } - private static bool ContainsInBlockBody(BlockSyntax block, TextSpan textSpan) - { - if (block == null) - { return false; } - var blockSpan = TextSpan.FromBounds(block.OpenBraceToken.Span.End, block.CloseBraceToken.SpanStart); - return blockSpan.Contains(textSpan); - } - - private static bool ContainsInExpressionBodiedMemberBody(ArrowExpressionClauseSyntax expressionBodiedMember, TextSpan textSpan) - { - if (expressionBodiedMember == null) + private static bool ContainsInBlockBody(BlockSyntax block, TextSpan textSpan) { - return false; - } - - var expressionBodiedMemberBody = TextSpan.FromBounds(expressionBodiedMember.Expression.SpanStart, expressionBodiedMember.Expression.Span.End); - return expressionBodiedMemberBody.Contains(textSpan); - } - - private static SelectionInfo CheckErrorCasesAndAppendDescriptions( - SelectionInfo selectionInfo, - SyntaxNode root) - { - if (selectionInfo.Status.Failed) - return selectionInfo; - - if (selectionInfo.FirstTokenInFinalSpan.IsMissing || selectionInfo.LastTokenInFinalSpan.IsMissing) - { - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.Contains_invalid_selection)); - } - - // get the node that covers the selection - var commonNode = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); - - if ((selectionInfo.SelectionInExpression || selectionInfo.SelectionInSingleStatement) && commonNode.HasDiagnostics()) - { - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.The_selection_contains_syntactic_errors)); - } + if (block == null) + { + return false; + } - var tokens = root.DescendantTokens(selectionInfo.FinalSpan); - if (tokens.ContainPreprocessorCrossOver(selectionInfo.FinalSpan)) - { - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: true, CSharpFeaturesResources.Selection_can_not_cross_over_preprocessor_directives)); + var blockSpan = TextSpan.FromBounds(block.OpenBraceToken.Span.End, block.CloseBraceToken.SpanStart); + return blockSpan.Contains(textSpan); } - // TODO : check whether this can be handled by control flow analysis engine - if (tokens.Any(t => t.Kind() == SyntaxKind.YieldKeyword)) + private static bool ContainsInExpressionBodiedMemberBody(ArrowExpressionClauseSyntax expressionBodiedMember, TextSpan textSpan) { - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: true, CSharpFeaturesResources.Selection_can_not_contain_a_yield_statement)); - } + if (expressionBodiedMember == null) + { + return false; + } - // TODO : check behavior of control flow analysis engine around exception and exception handling. - if (tokens.ContainArgumentlessThrowWithoutEnclosingCatch(selectionInfo.FinalSpan)) - { - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: true, CSharpFeaturesResources.Selection_can_not_contain_throw_statement)); + var expressionBodiedMemberBody = TextSpan.FromBounds(expressionBodiedMember.Expression.SpanStart, expressionBodiedMember.Expression.Span.End); + return expressionBodiedMemberBody.Contains(textSpan); } - if (selectionInfo.SelectionInExpression && commonNode.PartOfConstantInitializerExpression()) + private static SelectionInfo CheckErrorCasesAndAppendDescriptions( + SelectionInfo selectionInfo, + SyntaxNode root) { - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.Selection_can_not_be_part_of_constant_initializer_expression)); - } + if (selectionInfo.Status.Failed) + return selectionInfo; - if (commonNode.IsUnsafeContext()) - { - selectionInfo = selectionInfo.WithStatus(s => s.With(s.Succeeded, CSharpFeaturesResources.The_selected_code_is_inside_an_unsafe_context)); - } + if (selectionInfo.FirstTokenInFinalSpan.IsMissing || selectionInfo.LastTokenInFinalSpan.IsMissing) + { + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: false, CSharpFeaturesResources.Contains_invalid_selection) + }; + } - // For now patterns are being blanket disabled for extract method. This issue covers designing extractions for them - // and re-enabling this. - // https://github.com/dotnet/roslyn/issues/9244 - if (commonNode.Kind() == SyntaxKind.IsPatternExpression) - { - selectionInfo = selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.Selection_can_not_contain_a_pattern_expression)); - } + // get the node that covers the selection + var commonNode = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); - return selectionInfo; - } + if ((selectionInfo.SelectionInExpression || selectionInfo.SelectionInSingleStatement) && commonNode.HasDiagnostics()) + { + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: false, CSharpFeaturesResources.The_selection_contains_syntactic_errors), + }; + } - private static SelectionInfo AssignInitialFinalTokens(SelectionInfo selectionInfo, SyntaxNode root, CancellationToken cancellationToken) - { - if (selectionInfo.Status.Failed) - return selectionInfo; + var tokens = root.DescendantTokens(selectionInfo.FinalSpan); + if (tokens.ContainPreprocessorCrossOver(selectionInfo.FinalSpan)) + { + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: true, CSharpFeaturesResources.Selection_can_not_cross_over_preprocessor_directives), + }; + } - if (selectionInfo.SelectionInExpression) - { - // simple expression case - return selectionInfo.With(s => s.FirstTokenInFinalSpan = s.CommonRootFromOriginalSpan.GetFirstToken(includeZeroWidth: true)) - .With(s => s.LastTokenInFinalSpan = s.CommonRootFromOriginalSpan.GetLastToken(includeZeroWidth: true)); - } + // TODO : check whether this can be handled by control flow analysis engine + if (tokens.Any(t => t.Kind() == SyntaxKind.YieldKeyword)) + { + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: true, CSharpFeaturesResources.Selection_can_not_contain_a_yield_statement), + }; + } - var range = GetStatementRangeContainingSpan( - CSharpSyntaxFacts.Instance, - root, TextSpan.FromBounds(selectionInfo.FirstTokenInOriginalSpan.SpanStart, selectionInfo.LastTokenInOriginalSpan.Span.End), - cancellationToken); + // TODO : check behavior of control flow analysis engine around exception and exception handling. + if (tokens.ContainArgumentlessThrowWithoutEnclosingCatch(selectionInfo.FinalSpan)) + { + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: true, CSharpFeaturesResources.Selection_can_not_contain_throw_statement), + }; + } - if (range == null) - { - return selectionInfo.WithStatus(s => s.With(succeeded: false, CSharpFeaturesResources.No_valid_statement_range_to_extract)); - } + if (selectionInfo.SelectionInExpression && commonNode.PartOfConstantInitializerExpression()) + { + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: false, CSharpFeaturesResources.Selection_can_not_be_part_of_constant_initializer_expression), + }; + } - var statement1 = range.Value.Item1; - var statement2 = range.Value.Item2; + if (commonNode.IsUnsafeContext()) + { + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(selectionInfo.Status.Succeeded, CSharpFeaturesResources.The_selected_code_is_inside_an_unsafe_context), + }; + } - if (statement1 == statement2) - { - // check one more time to see whether it is an expression case - var expression = selectionInfo.CommonRootFromOriginalSpan.GetAncestor(); - if (expression != null && statement1.Span.Contains(expression.Span)) + // For now patterns are being blanket disabled for extract method. This issue covers designing extractions for them + // and re-enabling this. + // https://github.com/dotnet/roslyn/issues/9244 + if (commonNode.Kind() == SyntaxKind.IsPatternExpression) { - return selectionInfo.With(s => s.SelectionInExpression = true) - .With(s => s.FirstTokenInFinalSpan = expression.GetFirstToken(includeZeroWidth: true)) - .With(s => s.LastTokenInFinalSpan = expression.GetLastToken(includeZeroWidth: true)); + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: false, CSharpFeaturesResources.Selection_can_not_contain_a_pattern_expression), + }; } - // single statement case - return selectionInfo.With(s => s.SelectionInSingleStatement = true) - .With(s => s.FirstTokenInFinalSpan = statement1.GetFirstToken(includeZeroWidth: true)) - .With(s => s.LastTokenInFinalSpan = statement1.GetLastToken(includeZeroWidth: true)); + return selectionInfo; } - // move only statements inside of the block - return selectionInfo.With(s => s.FirstTokenInFinalSpan = statement1.GetFirstToken(includeZeroWidth: true)) - .With(s => s.LastTokenInFinalSpan = statement2.GetLastToken(includeZeroWidth: true)); - } + private SelectionInfo AssignInitialFinalTokens(SelectionInfo selectionInfo, SyntaxNode root, CancellationToken cancellationToken) + { + if (selectionInfo.Status.Failed) + return selectionInfo; - private static SelectionInfo AssignFinalSpan(SelectionInfo selectionInfo, SourceText text) - { - if (selectionInfo.Status.Failed) - return selectionInfo; + if (selectionInfo.SelectionInExpression) + { + // simple expression case + return selectionInfo with + { + FirstTokenInFinalSpan = selectionInfo.CommonRootFromOriginalSpan.GetFirstToken(includeZeroWidth: true), + LastTokenInFinalSpan = selectionInfo.CommonRootFromOriginalSpan.GetLastToken(includeZeroWidth: true), + }; + } - // set final span - var start = (selectionInfo.FirstTokenInOriginalSpan == selectionInfo.FirstTokenInFinalSpan) - ? Math.Min(selectionInfo.FirstTokenInOriginalSpan.SpanStart, selectionInfo.OriginalSpan.Start) - : selectionInfo.FirstTokenInFinalSpan.FullSpan.Start; + var range = GetStatementRangeContainingSpan( + root, TextSpan.FromBounds(selectionInfo.FirstTokenInOriginalSpan.SpanStart, selectionInfo.LastTokenInOriginalSpan.Span.End), + cancellationToken); - var end = (selectionInfo.LastTokenInOriginalSpan == selectionInfo.LastTokenInFinalSpan) - ? Math.Max(selectionInfo.LastTokenInOriginalSpan.Span.End, selectionInfo.OriginalSpan.End) - : selectionInfo.LastTokenInFinalSpan.FullSpan.End; + if (range is not var (firstStatement, lastStatement)) + { + return selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: false, FeaturesResources.No_valid_statement_range_to_extract), + }; + } - return selectionInfo.With(s => s.FinalSpan = GetAdjustedSpan(text, TextSpan.FromBounds(start, end))); - } + if (firstStatement == lastStatement) + { + // check one more time to see whether it is an expression case + var expression = selectionInfo.CommonRootFromOriginalSpan.GetAncestor(); + if (expression != null && firstStatement.Span.Contains(expression.Span)) + { + return selectionInfo with + { + SelectionInExpression = true, + FirstTokenInFinalSpan = expression.GetFirstToken(includeZeroWidth: true), + LastTokenInFinalSpan = expression.GetLastToken(includeZeroWidth: true), + }; + } - public override bool ContainsNonReturnExitPointsStatements(IEnumerable jumpsOutOfRegion) - => jumpsOutOfRegion.Where(n => n is not ReturnStatementSyntax).Any(); + // single statement case + return selectionInfo with + { + SelectionInSingleStatement = true, + FirstTokenInFinalSpan = firstStatement.GetFirstToken(includeZeroWidth: true), + LastTokenInFinalSpan = firstStatement.GetLastToken(includeZeroWidth: true), + }; + } - public override IEnumerable GetOuterReturnStatements(SyntaxNode commonRoot, IEnumerable jumpsOutOfRegion) - { - var returnStatements = jumpsOutOfRegion.Where(s => s is ReturnStatementSyntax); + // move only statements inside of the block + return selectionInfo with + { + FirstTokenInFinalSpan = firstStatement.GetFirstToken(includeZeroWidth: true), + LastTokenInFinalSpan = lastStatement.GetLastToken(includeZeroWidth: true), + }; + } - var container = commonRoot.GetAncestorsOrThis().Where(a => a.IsReturnableConstruct()).FirstOrDefault(); - if (container == null) - return []; + private static SelectionInfo AssignFinalSpan(SelectionInfo selectionInfo, SourceText text) + { + if (selectionInfo.Status.Failed) + return selectionInfo; - var returnableConstructPairs = returnStatements.Select(r => (r, r.GetAncestors().Where(a => a.IsReturnableConstruct()).FirstOrDefault())) - .Where(p => p.Item2 != null); + // set final span + var start = selectionInfo.FirstTokenInOriginalSpan == selectionInfo.FirstTokenInFinalSpan + ? Math.Min(selectionInfo.FirstTokenInOriginalSpan.SpanStart, selectionInfo.OriginalSpan.Start) + : selectionInfo.FirstTokenInFinalSpan.FullSpan.Start; - // now filter return statements to only include the one under outmost container - return returnableConstructPairs.Where(p => p.Item2 == container).Select(p => p.Item1); - } + var end = selectionInfo.LastTokenInOriginalSpan == selectionInfo.LastTokenInFinalSpan + ? Math.Max(selectionInfo.LastTokenInOriginalSpan.Span.End, selectionInfo.OriginalSpan.End) + : selectionInfo.LastTokenInFinalSpan.FullSpan.End; - public override bool IsFinalSpanSemanticallyValidSpan( - SyntaxNode root, TextSpan textSpan, - IEnumerable returnStatements, CancellationToken cancellationToken) - { - // return statement shouldn't contain any return value - if (returnStatements.Cast().Any(r => r.Expression != null)) - { - return false; + return selectionInfo with + { + FinalSpan = GetAdjustedSpan(text, TextSpan.FromBounds(start, end)), + }; } - var lastToken = root.FindToken(textSpan.End); - if (lastToken.Kind() == SyntaxKind.None) - { - return false; - } + public override bool ContainsNonReturnExitPointsStatements(ImmutableArray jumpsOutOfRegion) + => jumpsOutOfRegion.Any(n => n is not ReturnStatementSyntax); - var container = lastToken.GetAncestors().FirstOrDefault(n => n.IsReturnableConstruct()); - if (container == null) + public override ImmutableArray GetOuterReturnStatements(SyntaxNode commonRoot, ImmutableArray jumpsOutOfRegion) { - return false; - } + var container = commonRoot.GetAncestorsOrThis().Where(a => a.IsReturnableConstruct()).FirstOrDefault(); + if (container == null) + return []; - var body = container.GetBlockBody(); - if (body == null) - { - return false; + // now filter return statements to only include the one under outmost container + return jumpsOutOfRegion + .OfType() + .Select(returnStatement => (returnStatement, container: returnStatement.GetAncestors().Where(a => a.IsReturnableConstruct()).FirstOrDefault())) + .Where(p => p.container == container) + .SelectAsArray(p => p.returnStatement) + .CastArray(); } - // make sure that next token of the last token in the selection is the close braces of containing block - if (body.CloseBraceToken != lastToken.GetNextToken(includeZeroWidth: true)) + public override bool IsFinalSpanSemanticallyValidSpan( + TextSpan textSpan, ImmutableArray returnStatements, CancellationToken cancellationToken) { - return false; - } + // return statement shouldn't contain any return value + if (returnStatements.Cast().Any(r => r.Expression != null)) + { + return false; + } - // alright, for these constructs, it must be okay to be extracted - switch (container.Kind()) - { - case SyntaxKind.AnonymousMethodExpression: - case SyntaxKind.SimpleLambdaExpression: - case SyntaxKind.ParenthesizedLambdaExpression: - return true; - } + var lastToken = this.SemanticDocument.Root.FindToken(textSpan.End); + if (lastToken.Kind() == SyntaxKind.None) + { + return false; + } - // now, only method is okay to be extracted out - if (body.Parent is not MethodDeclarationSyntax method) - { - return false; - } + var container = lastToken.GetAncestors().FirstOrDefault(n => n.IsReturnableConstruct()); + if (container == null) + { + return false; + } - // make sure this method doesn't have return type. - return method.ReturnType is PredefinedTypeSyntax p && - p.Keyword.Kind() == SyntaxKind.VoidKeyword; - } + var body = container.GetBlockBody(); + if (body == null) + { + return false; + } - private static TextSpan GetAdjustedSpan(SourceText text, TextSpan textSpan) - { - // beginning of a file - if (textSpan.IsEmpty || textSpan.End == 0) - { - return textSpan; + // make sure that next token of the last token in the selection is the close braces of containing block + if (body.CloseBraceToken != lastToken.GetNextToken(includeZeroWidth: true)) + { + return false; + } + + // alright, for these constructs, it must be okay to be extracted + switch (container.Kind()) + { + case SyntaxKind.AnonymousMethodExpression: + case SyntaxKind.SimpleLambdaExpression: + case SyntaxKind.ParenthesizedLambdaExpression: + return true; + } + + // now, only method is okay to be extracted out + if (body.Parent is not MethodDeclarationSyntax method) + { + return false; + } + + // make sure this method doesn't have return type. + return method.ReturnType is PredefinedTypeSyntax p && + p.Keyword.Kind() == SyntaxKind.VoidKeyword; } - // if it is a start of new line, make it belong to previous line - var line = text.Lines.GetLineFromPosition(textSpan.End); - if (line.Start != textSpan.End) + private static TextSpan GetAdjustedSpan(SourceText text, TextSpan textSpan) { - return textSpan; - } + // beginning of a file + if (textSpan.IsEmpty || textSpan.End == 0) + { + return textSpan; + } - // get previous line - Contract.ThrowIfFalse(line.LineNumber > 0); - var previousLine = text.Lines[line.LineNumber - 1]; + // if it is a start of new line, make it belong to previous line + var line = text.Lines.GetLineFromPosition(textSpan.End); + if (line.Start != textSpan.End) + { + return textSpan; + } - // if the span is past the end of the line (ie, in whitespace) then - // return to the end of the line including whitespace - if (textSpan.Start > previousLine.End) - { - return TextSpan.FromBounds(textSpan.Start, previousLine.EndIncludingLineBreak); - } + // get previous line + Contract.ThrowIfFalse(line.LineNumber > 0); + var previousLine = text.Lines[line.LineNumber - 1]; - return TextSpan.FromBounds(textSpan.Start, previousLine.End); + // if the span is past the end of the line (ie, in whitespace) then + // return to the end of the line including whitespace + if (textSpan.Start > previousLine.End) + { + return TextSpan.FromBounds(textSpan.Start, previousLine.EndIncludingLineBreak); + } + + return TextSpan.FromBounds(textSpan.Start, previousLine.End); + } } } diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf index 840ed70b6f352..ac6530d1e2827 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf @@ -132,11 +132,6 @@ Automatický výběr je zakázaný kvůli možné deklaraci proměnné vzoru. - - Cannot determine valid range of statements to extract - Nelze určit platný rozsah příkazů k extrakci. - - Change to 'as' expression Změnit na výraz as @@ -367,11 +362,6 @@ Vybraný uzel je uvnitř nezabezpečeného kontextu. - - No valid statement range to extract - Žádný platný rozsah příkazů pro extrakci - - deprecated zastaralé diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf index 9617804b220f1..aae32d98257c1 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf @@ -132,11 +132,6 @@ Automatische Auswahl aufgrund einer potenziellen Mustervariablendeklaration deaktiviert. - - Cannot determine valid range of statements to extract - Ein gültiger Bereich der zu extrahierenden Anweisungen kann nicht ermittelt werden. - - Change to 'as' expression In as-Ausdruck ändern @@ -367,11 +362,6 @@ Der ausgewählte Code befindet sich in einem unsicheren Kontext. - - No valid statement range to extract - Kein gültiger Anweisungsbereich für die Extraktion - - deprecated veraltet diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf index 4dd0c1e42f791..fe14d989b72a2 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf @@ -132,11 +132,6 @@ La selección automática se ha deshabilitado debido a una posible declaración de variable de patrón. - - Cannot determine valid range of statements to extract - No se puede determinar el intervalo válido de instrucciones que se van a extraer - - Change to 'as' expression Cambiar a la expresión "as" @@ -367,11 +362,6 @@ El código seleccionado se encuentra dentro de un contexto no seguro. - - No valid statement range to extract - No hay un intervalo válido de instrucciones para extraer. - - deprecated en desuso diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf index 536e1cb762800..f8d1e09858cb0 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf @@ -132,11 +132,6 @@ Sélection automatique désactivée en raison d'une déclaration de variable de modèle éventuelle. - - Cannot determine valid range of statements to extract - Impossible de déterminer la plage valide des instructions à extraire - - Change to 'as' expression Changer en expression 'as' @@ -367,11 +362,6 @@ Le code sélectionné se trouve dans un contexte unsafe. - - No valid statement range to extract - Aucune plage valide d'instructions à extraire - - deprecated déconseillé diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf index 450e67325d3c1..f3a49d115eb0c 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf @@ -132,11 +132,6 @@ La selezione automatica è disabilitata a causa di una potenziale dichiarazione della variabile di criterio. - - Cannot determine valid range of statements to extract - Non è possibile determinare l'intervallo valido di istruzioni da estrarre - - Change to 'as' expression Cambia in espressione 'as' @@ -367,11 +362,6 @@ Il codice selezionato è all'interno di un contesto unsafe. - - No valid statement range to extract - Non esiste alcun intervallo di istruzioni valido per l'estrazione - - deprecated deprecato diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf index e69b505ccb5e9..573011e7c6c91 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf @@ -132,11 +132,6 @@ パターン変数宣言の可能性があるため、自動選択は無効になっています。 - - Cannot determine valid range of statements to extract - 抽出するステートメントの有効な範囲を決定できません - - Change to 'as' expression 'as' 式に変更 @@ -367,11 +362,6 @@ 選択されたコードは unsafe コンテキストの内側にあります。 - - No valid statement range to extract - 抽出する有効なステートメントの範囲がありません - - deprecated 非推奨 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf index 322f3ed3167a5..ade477529e3f7 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf @@ -132,11 +132,6 @@ 잠재적인 패턴 변수 선언으로 인해 자동 선택을 사용하지 않도록 설정했습니다. - - Cannot determine valid range of statements to extract - 추출할 문의 유효한 범위를 결정할 수 없습니다. - - Change to 'as' expression 'as' 식으로 변경 @@ -367,11 +362,6 @@ 선택한 코드가 안전하지 않은 컨텍스트 내에 있습니다. - - No valid statement range to extract - 추출하는 데 유효한 문 범위가 없습니다. - - deprecated 사용되지 않음 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf index 0921400a5c98a..2b69c53302241 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf @@ -132,11 +132,6 @@ Automatyczne wybieranie zostało wyłączone z powodu możliwej deklaracji zmiennej wzorca. - - Cannot determine valid range of statements to extract - Nie można określić prawidłowego zakresu instrukcji do wyodrębnienia - - Change to 'as' expression Zmień na wyrażenie „as” @@ -367,11 +362,6 @@ Zaznaczony kod znajduje się w niebezpiecznym kontekście. - - No valid statement range to extract - Brak prawidłowego zakresu instrukcji do wyodrębnienia - - deprecated przestarzałe diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf index fe969acd32d7c..9ea35d3b6d8f4 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf @@ -132,11 +132,6 @@ Seleção automática desabilitada devido a uma possível declaração de variável de padrão. - - Cannot determine valid range of statements to extract - Não é possível determinar o intervalo válido de instruções a serem extraídas - - Change to 'as' expression Alterar para a expressão 'as' @@ -367,11 +362,6 @@ O código selecionado está dentro de um contexto sem segurança. - - No valid statement range to extract - Nenhum intervalo de instruções válido para extrair - - deprecated preterido diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf index 1940b6947d921..4b0fdd2bf1c9e 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf @@ -132,11 +132,6 @@ Автовыбор отключен из-за возможного объявления переменной шаблона. - - Cannot determine valid range of statements to extract - Невозможно определить допустимый диапазон операторов для извлечения - - Change to 'as' expression Изменить на выражение "as" @@ -367,11 +362,6 @@ Выделенный код находится внутри небезопасного содержимого. - - No valid statement range to extract - Отсутствует допустимый диапазон операторов для извлечения - - deprecated не рекомендуется diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf index 4522b9b02cd01..3a6fd3dc9de2c 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf @@ -132,11 +132,6 @@ Otomatik seçim, olası desen değişkeni bildirimi nedeniyle devre dışı bırakıldı. - - Cannot determine valid range of statements to extract - Ayıklanacak geçerli deyim aralığı belirlenemedi - - Change to 'as' expression 'as' ifadesine değiştir @@ -367,11 +362,6 @@ Seçili kod güvenli olmayan bir bağlam içinde. - - No valid statement range to extract - Ayıklanacak geçerli deyim aralığı yok - - deprecated kullanım dışı diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf index d8d192ffabe3c..23eb24bf84ce9 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf @@ -132,11 +132,6 @@ 由于可能出现模式变量声明,已禁用自动选择。 - - Cannot determine valid range of statements to extract - 无法确定要提取的语句的有效范围 - - Change to 'as' expression 更改为 "as" 表达式 @@ -367,11 +362,6 @@ 所选代码位于不安全的上下文中。 - - No valid statement range to extract - 没有可供提取的有效语句范围 - - deprecated 弃用的 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf index 91f7d6902012b..0e86fa0ef137f 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf @@ -132,11 +132,6 @@ 因為潛在的範圍變數宣告,所以停用了自動選取。 - - Cannot determine valid range of statements to extract - 無法判斷要擷取的有效陳述式範圍 - - Change to 'as' expression 變更為 'as' 運算式 @@ -367,11 +362,6 @@ 選取的程式碼在不安全的內容中。 - - No valid statement range to extract - 沒有可擷取的有效陳述式範圍 - - deprecated 已取代 diff --git a/src/Features/Core/Portable/ExtractMethod/AbstractExtractMethodService.cs b/src/Features/Core/Portable/ExtractMethod/AbstractExtractMethodService.cs index e29634f9a690d..bf125827591b2 100644 --- a/src/Features/Core/Portable/ExtractMethod/AbstractExtractMethodService.cs +++ b/src/Features/Core/Portable/ExtractMethod/AbstractExtractMethodService.cs @@ -8,16 +8,25 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract class AbstractExtractMethodService< +/// +/// Core service that tries to share as much extract-method logic across C# and VB. Note: TStatementSyntax and +/// TExecutableStatementSyntax exist to model VB's inheritance model there (where StatementSyntax is used liberally +/// (including for signatures of members, while ExecutableStatementSyntax generally corresponds to a code statement +/// found within a method body). In C# these will be the same StatementSyntax type as C# has a much stronger split +/// between executable code statements and symbol signatures. +/// +internal abstract partial class AbstractExtractMethodService< TValidator, TExtractor, TSelectionResult, TStatementSyntax, + TExecutableStatementSyntax, TExpressionSyntax> : IExtractMethodService - where TValidator : SelectionValidator - where TExtractor : MethodExtractor - where TSelectionResult : SelectionResult + where TValidator : AbstractExtractMethodService.SelectionValidator + where TExtractor : AbstractExtractMethodService.MethodExtractor + where TSelectionResult : AbstractExtractMethodService.SelectionResult where TStatementSyntax : SyntaxNode + where TExecutableStatementSyntax : TStatementSyntax where TExpressionSyntax : SyntaxNode { protected abstract TValidator CreateSelectionValidator(SemanticDocument document, TextSpan textSpan, bool localFunction); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.SymbolMapBuilder.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.SymbolMapBuilder.cs index 6fb03e2f8ca4d..c6fc13850575a 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.SymbolMapBuilder.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.SymbolMapBuilder.cs @@ -13,66 +13,75 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - protected abstract partial class Analyzer + internal abstract partial class MethodExtractor { - private sealed class SymbolMapBuilder + protected abstract partial class Analyzer { - private readonly SemanticModel _semanticModel; - private readonly ISyntaxFactsService _service; - private readonly TextSpan _span; - private readonly Dictionary> _symbolMap = []; - private readonly CancellationToken _cancellationToken; - - public static Dictionary> Build( - ISyntaxFactsService service, - SemanticModel semanticModel, - SyntaxNode root, - TextSpan span, - CancellationToken cancellationToken) + private sealed class SymbolMapBuilder { - Contract.ThrowIfNull(semanticModel); - Contract.ThrowIfNull(service); - Contract.ThrowIfNull(root); + private readonly SemanticModel _semanticModel; + private readonly ISyntaxFactsService _service; + private readonly TextSpan _span; + private readonly Dictionary> _symbolMap = []; + private readonly CancellationToken _cancellationToken; - var builder = new SymbolMapBuilder(service, semanticModel, span, cancellationToken); - builder.Visit(root); + public static Dictionary> Build( + ISyntaxFactsService service, + SemanticModel semanticModel, + SyntaxNode root, + TextSpan span, + CancellationToken cancellationToken) + { + Contract.ThrowIfNull(semanticModel); + Contract.ThrowIfNull(service); + Contract.ThrowIfNull(root); - return builder._symbolMap; - } + var builder = new SymbolMapBuilder(service, semanticModel, span, cancellationToken); + builder.Visit(root); - private SymbolMapBuilder( - ISyntaxFactsService service, - SemanticModel semanticModel, - TextSpan span, - CancellationToken cancellationToken) - { - _semanticModel = semanticModel; - _service = service; - _span = span; - _cancellationToken = cancellationToken; - } + return builder._symbolMap; + } - private void Visit(SyntaxNode node) - { - foreach (var token in node.DescendantTokens()) + private SymbolMapBuilder( + ISyntaxFactsService service, + SemanticModel semanticModel, + TextSpan span, + CancellationToken cancellationToken) { - if (token.IsMissing || - token.Width() <= 0 || - !_service.IsIdentifier(token) || - !_span.Contains(token.Span) || - _service.IsNameOfNamedArgument(token.Parent)) - { - continue; - } + _semanticModel = semanticModel; + _service = service; + _span = span; + _cancellationToken = cancellationToken; + } - var symbolInfo = _semanticModel.GetSymbolInfo(token, _cancellationToken); - foreach (var sym in symbolInfo.GetAllSymbols()) + private void Visit(SyntaxNode node) + { + foreach (var token in node.DescendantTokens()) { - // add binding result to map - var list = _symbolMap.GetOrAdd(sym, _ => []); - list.Add(token); + if (token.IsMissing || + token.Width() <= 0 || + !_service.IsIdentifier(token) || + !_span.Contains(token.Span) || + _service.IsNameOfNamedArgument(token.Parent)) + { + continue; + } + + var symbolInfo = _semanticModel.GetSymbolInfo(token, _cancellationToken); + foreach (var sym in symbolInfo.GetAllSymbols()) + { + // add binding result to map + var list = _symbolMap.GetOrAdd(sym, _ => []); + list.Add(token); + } } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs index f1b057467dc9e..6130012c70d8b 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs @@ -17,1032 +17,979 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - protected abstract partial class Analyzer + internal abstract partial class MethodExtractor { - private readonly SemanticDocument _semanticDocument; + protected abstract partial class Analyzer + { + protected readonly CancellationToken CancellationToken; + protected readonly TSelectionResult SelectionResult; + protected readonly bool LocalFunction; - protected readonly CancellationToken CancellationToken; - protected readonly TSelectionResult SelectionResult; - protected readonly bool LocalFunction; + private readonly HashSet _nonNoisySyntaxKindSet; - protected ISemanticFactsService SemanticFacts => _semanticDocument.Document.GetRequiredLanguageService(); - protected ISyntaxFactsService SyntaxFacts => _semanticDocument.Document.GetRequiredLanguageService(); + private SemanticDocument SemanticDocument => SelectionResult.SemanticDocument; + protected SemanticModel SemanticModel => SemanticDocument.SemanticModel; - protected Analyzer(TSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(selectionResult); + protected ISemanticFactsService SemanticFacts => this.SemanticDocument.Document.GetRequiredLanguageService(); + protected ISyntaxFactsService SyntaxFacts => this.SemanticDocument.Document.GetRequiredLanguageService(); - SelectionResult = selectionResult; - _semanticDocument = selectionResult.SemanticDocument; - CancellationToken = cancellationToken; - LocalFunction = localFunction; - } + protected Analyzer(TSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(selectionResult); - /// - /// convert text span to node range for the flow analysis API - /// - private (TStatementSyntax firstStatement, TStatementSyntax lastStatement) GetFlowAnalysisNodeRange() - { - var first = this.SelectionResult.GetFirstStatement(); - var last = this.SelectionResult.GetLastStatement(); + SelectionResult = selectionResult; + CancellationToken = cancellationToken; + LocalFunction = localFunction; - // single statement case - if (first == last || - first.Span.Contains(last.Span)) - { - return (first, first); + var syntaxKinds = this.SyntaxFacts.SyntaxKinds; + _nonNoisySyntaxKindSet = [syntaxKinds.WhitespaceTrivia, syntaxKinds.EndOfLineTrivia]; } - // multiple statement case - var firstUnderContainer = this.SelectionResult.GetFirstStatementUnderContainer(); - var lastUnderContainer = this.SelectionResult.GetLastStatementUnderContainer(); - return (firstUnderContainer, lastUnderContainer); - } - - protected abstract bool IsInPrimaryConstructorBaseType(); + protected abstract bool IsInPrimaryConstructorBaseType(); - /// - /// check whether selection contains return statement or not - /// - protected abstract bool ContainsReturnStatementInSelectedCode(IEnumerable jumpOutOfRegionStatements); + /// + /// check whether selection contains return statement or not + /// + protected abstract bool ContainsReturnStatementInSelectedCode(ImmutableArray exitPoints); - /// - /// create VariableInfo type - /// - protected abstract VariableInfo CreateFromSymbol(ISymbol symbol, ITypeSymbol type, VariableStyle variableStyle, bool variableDeclared); + /// + /// create VariableInfo type + /// + protected abstract VariableInfo CreateFromSymbol(ISymbol symbol, ITypeSymbol type, VariableStyle variableStyle, bool variableDeclared); - protected virtual bool IsReadOutside(ISymbol symbol, HashSet readOutsideMap) - => readOutsideMap.Contains(symbol); + protected virtual bool IsReadOutside(ISymbol symbol, HashSet readOutsideMap) + => readOutsideMap.Contains(symbol); - protected abstract bool TreatOutAsRef { get; } + protected abstract bool TreatOutAsRef { get; } - /// - /// get type of the range variable symbol - /// - protected abstract ITypeSymbol? GetRangeVariableType(SemanticModel model, IRangeVariableSymbol symbol); + /// + /// get type of the range variable symbol + /// + protected abstract ITypeSymbol? GetRangeVariableType(IRangeVariableSymbol symbol); - /// - /// check whether the selection is at the placed where read-only field is allowed to be extracted out - /// - /// - protected abstract bool ReadOnlyFieldAllowed(); + /// + /// check whether the selection is at the placed where read-only field is allowed to be extracted out + /// + /// + protected abstract bool ReadOnlyFieldAllowed(); - public AnalyzerResult Analyze() - { - // do data flow analysis - var model = _semanticDocument.SemanticModel; - var dataFlowAnalysisData = GetDataFlowAnalysisData(model); + public AnalyzerResult Analyze() + { + // do data flow analysis + var model = this.SemanticDocument.SemanticModel; + var dataFlowAnalysisData = GetDataFlowAnalysisData(); - // build symbol map for the identifiers used inside of the selection - var symbolMap = GetSymbolMap(model); + // build symbol map for the identifiers used inside of the selection + var symbolMap = GetSymbolMap(); - var isInPrimaryConstructorBaseType = this.IsInPrimaryConstructorBaseType(); + var isInPrimaryConstructorBaseType = this.IsInPrimaryConstructorBaseType(); - // gather initial local or parameter variable info - GenerateVariableInfoMap( - bestEffort: false, model, dataFlowAnalysisData, symbolMap, isInPrimaryConstructorBaseType, out var variableInfoMap, out var failedVariables); - if (failedVariables.Count > 0) - { - // If we weren't able to figure something out, go back and regenerate the map - // this time in 'best effort' mode. We'll give the user a message saying there - // was a problem, but we allow them to proceed so they're not unnecessarily - // blocked just because we didn't understand something. + // gather initial local or parameter variable info GenerateVariableInfoMap( - bestEffort: true, model, dataFlowAnalysisData, symbolMap, isInPrimaryConstructorBaseType, out variableInfoMap, out var unused); - Contract.ThrowIfFalse(unused.Count == 0); - } + bestEffort: false, dataFlowAnalysisData, symbolMap, isInPrimaryConstructorBaseType, out var variableInfoMap, out var failedVariables); + if (failedVariables.Count > 0) + { + // If we weren't able to figure something out, go back and regenerate the map + // this time in 'best effort' mode. We'll give the user a message saying there + // was a problem, but we allow them to proceed so they're not unnecessarily + // blocked just because we didn't understand something. + GenerateVariableInfoMap( + bestEffort: true, dataFlowAnalysisData, symbolMap, isInPrimaryConstructorBaseType, out variableInfoMap, out var unused); + Contract.ThrowIfFalse(unused.Count == 0); + } - var thisParameterBeingRead = (IParameterSymbol?)dataFlowAnalysisData.ReadInside.FirstOrDefault(IsThisParameter); - var isThisParameterWritten = dataFlowAnalysisData.WrittenInside.Any(static s => IsThisParameter(s)); - - // Need to generate an instance method if any primary constructor parameter is read or written inside the - // selection. This does not apply if we're in the base-type-list as that will still need a static method. - var primaryConstructorParameterReadOrWritten = !isInPrimaryConstructorBaseType && dataFlowAnalysisData.ReadInside - .Concat(dataFlowAnalysisData.WrittenInside) - .OfType() - .FirstOrDefault(s => s.IsPrimaryConstructor(this.CancellationToken)) != null; - - var localFunctionCallsNotWithinSpan = symbolMap.Keys.Where(s => s.IsLocalFunction() && !s.Locations.Any(static (l, self) => self.SelectionResult.FinalSpan.Contains(l.SourceSpan), this)); - - // Checks to see if selection includes a local function call + if the given local function declaration is not included in the selection. - var containsAnyLocalFunctionCallNotWithinSpan = localFunctionCallsNotWithinSpan.Any(); - - // Checks to see if selection includes a non-static local function call + if the given local function declaration is not included in the selection. - var containsNonStaticLocalFunctionCallNotWithinSpan = containsAnyLocalFunctionCallNotWithinSpan && localFunctionCallsNotWithinSpan.Where(s => !s.IsStatic).Any(); - - var instanceMemberIsUsed = thisParameterBeingRead != null - || isThisParameterWritten - || containsNonStaticLocalFunctionCallNotWithinSpan - || primaryConstructorParameterReadOrWritten; - - var shouldBeReadOnly = !isThisParameterWritten - && thisParameterBeingRead != null - && thisParameterBeingRead.Type is { TypeKind: TypeKind.Struct, IsReadOnly: false }; - - // check whether end of selection is reachable - var endOfSelectionReachable = IsEndOfSelectionReachable(model); - - // collects various variable informations - // extracted code contains return value - var isInExpressionOrHasReturnStatement = IsInExpressionOrHasReturnStatement(model); - var (parameters, returnType, returnsByRef, variablesToUseAsReturnValue, unsafeAddressTakenUsed) = - GetSignatureInformation(dataFlowAnalysisData, variableInfoMap, isInExpressionOrHasReturnStatement); - - (returnType, var returnTypeHasAnonymousType, var awaitTaskReturn) = AdjustReturnType(model, returnType); - - // collect method type variable used in selected code - var sortedMap = new SortedDictionary(); - var typeParametersInConstraintList = GetMethodTypeParametersInConstraintList(model, variableInfoMap, symbolMap, sortedMap); - var typeParametersInDeclaration = GetMethodTypeParametersInDeclaration(returnType, sortedMap); - - // check various error cases - var operationStatus = GetOperationStatus( - model, symbolMap, parameters, failedVariables, unsafeAddressTakenUsed, returnTypeHasAnonymousType, containsAnyLocalFunctionCallNotWithinSpan); - - return new AnalyzerResult( - typeParametersInDeclaration, - typeParametersInConstraintList, - parameters, - variablesToUseAsReturnValue, - returnType, - returnsByRef, - awaitTaskReturn, - instanceMemberIsUsed, - shouldBeReadOnly, - endOfSelectionReachable, - operationStatus); - } + var thisParameterBeingRead = (IParameterSymbol?)dataFlowAnalysisData.ReadInside.FirstOrDefault(IsThisParameter); + var isThisParameterWritten = dataFlowAnalysisData.WrittenInside.Any(static s => IsThisParameter(s)); - private (ITypeSymbol typeSymbol, bool hasAnonymousType, bool awaitTaskReturn) AdjustReturnType(SemanticModel model, ITypeSymbol returnType) - { - // check whether return type contains anonymous type and if it does, fix it up by making it object - var returnTypeHasAnonymousType = returnType.ContainsAnonymousType(); - returnType = returnTypeHasAnonymousType ? returnType.RemoveAnonymousTypes(model.Compilation) : returnType; - - // if selection contains await which is not under async lambda or anonymous delegate, - // change return type to be wrapped in Task - var shouldPutAsyncModifier = SelectionResult.CreateAsyncMethod(); - if (shouldPutAsyncModifier) - { - WrapReturnTypeInTask(model, ref returnType, out var awaitTaskReturn); + // Need to generate an instance method if any primary constructor parameter is read or written inside the + // selection. This does not apply if we're in the base-type-list as that will still need a static method. + var primaryConstructorParameterReadOrWritten = !isInPrimaryConstructorBaseType && dataFlowAnalysisData.ReadInside + .Concat(dataFlowAnalysisData.WrittenInside) + .OfType() + .FirstOrDefault(s => s.IsPrimaryConstructor(this.CancellationToken)) != null; - return (returnType, returnTypeHasAnonymousType, awaitTaskReturn); - } + var localFunctionCallsNotWithinSpan = symbolMap.Keys.Where(s => s.IsLocalFunction() && !s.Locations.Any(static (l, self) => self.SelectionResult.FinalSpan.Contains(l.SourceSpan), this)); - // unwrap task if needed - UnwrapTaskIfNeeded(model, ref returnType); - return (returnType, returnTypeHasAnonymousType, false); - } + // Checks to see if selection includes a local function call + if the given local function declaration is not included in the selection. + var containsAnyLocalFunctionCallNotWithinSpan = localFunctionCallsNotWithinSpan.Any(); - private void UnwrapTaskIfNeeded(SemanticModel model, ref ITypeSymbol returnType) - { - // nothing to unwrap - if (!SelectionResult.ContainingScopeHasAsyncKeyword() || - !ContainsReturnStatementInSelectedCode(model)) - { - return; - } + // Checks to see if selection includes a non-static local function call + if the given local function declaration is not included in the selection. + var containsNonStaticLocalFunctionCallNotWithinSpan = containsAnyLocalFunctionCallNotWithinSpan && localFunctionCallsNotWithinSpan.Where(s => !s.IsStatic).Any(); - var originalDefinition = returnType.OriginalDefinition; + var instanceMemberIsUsed = thisParameterBeingRead != null + || isThisParameterWritten + || containsNonStaticLocalFunctionCallNotWithinSpan + || primaryConstructorParameterReadOrWritten; - // see whether it needs to be unwrapped - var taskType = model.Compilation.TaskType(); - if (originalDefinition.Equals(taskType)) - { - returnType = model.Compilation.GetSpecialType(SpecialType.System_Void); - return; + var shouldBeReadOnly = !isThisParameterWritten + && thisParameterBeingRead is { Type: { TypeKind: TypeKind.Struct, IsReadOnly: false } }; + + // check whether end of selection is reachable + var endOfSelectionReachable = IsEndOfSelectionReachable(); + + var isInExpressionOrHasReturnStatement = IsInExpressionOrHasReturnStatement(); + + // check whether the selection contains "&" over a symbol exist + var unsafeAddressTakenUsed = dataFlowAnalysisData.UnsafeAddressTaken.Intersect(variableInfoMap.Keys).Any(); + var (parameters, returnType, returnsByRef, variablesToUseAsReturnValue) = + GetSignatureInformation(variableInfoMap, isInExpressionOrHasReturnStatement); + + (returnType, var awaitTaskReturn) = AdjustReturnType(returnType); + + // collect method type variable used in selected code + var sortedMap = new SortedDictionary(); + var typeParametersInConstraintList = GetMethodTypeParametersInConstraintList(variableInfoMap, symbolMap, sortedMap); + var typeParametersInDeclaration = GetMethodTypeParametersInDeclaration(returnType, sortedMap); + + // check various error cases + var operationStatus = GetOperationStatus( + symbolMap, parameters, failedVariables, unsafeAddressTakenUsed, returnType.ContainsAnonymousType(), containsAnyLocalFunctionCallNotWithinSpan); + + return new AnalyzerResult( + typeParametersInDeclaration, + typeParametersInConstraintList, + parameters, + variablesToUseAsReturnValue, + returnType, + returnsByRef, + awaitTaskReturn, + instanceMemberIsUsed, + shouldBeReadOnly, + endOfSelectionReachable, + operationStatus); } - var genericTaskType = model.Compilation.TaskOfTType(); - if (originalDefinition.Equals(genericTaskType)) + private (ITypeSymbol typeSymbol, bool awaitTaskReturn) AdjustReturnType(ITypeSymbol returnType) { - returnType = ((INamedTypeSymbol)returnType).TypeArguments[0]; - return; + // if selection contains await which is not under async lambda or anonymous delegate, + // change return type to be wrapped in Task + var shouldPutAsyncModifier = SelectionResult.CreateAsyncMethod(); + if (shouldPutAsyncModifier) + return WrapReturnTypeInTask(returnType); + + // unwrap task if needed + return (UnwrapTaskIfNeeded(returnType), awaitTaskReturn: false); } - // nothing to unwrap - return; - } + private ITypeSymbol UnwrapTaskIfNeeded(ITypeSymbol returnType) + { + // nothing to unwrap + if (SelectionResult.ContainingScopeHasAsyncKeyword() && + ContainsReturnStatementInSelectedCode()) + { + var originalDefinition = returnType.OriginalDefinition; - private void WrapReturnTypeInTask(SemanticModel model, ref ITypeSymbol returnType, out bool awaitTaskReturn) - { - awaitTaskReturn = false; + // see whether it needs to be unwrapped + var model = this.SemanticDocument.SemanticModel; + var taskType = model.Compilation.TaskType(); + if (originalDefinition.Equals(taskType)) + return model.Compilation.GetSpecialType(SpecialType.System_Void); - var taskType = model.Compilation.TaskType(); + var genericTaskType = model.Compilation.TaskOfTType(); + if (originalDefinition.Equals(genericTaskType)) + return ((INamedTypeSymbol)returnType).TypeArguments[0]; + } - if (taskType is object && returnType.Equals(model.Compilation.GetSpecialType(SpecialType.System_Void))) - { - // convert void to Task type - awaitTaskReturn = true; - returnType = taskType; - return; + // nothing to unwrap + return returnType; } - if (!SelectionResult.SelectionInExpression && ContainsReturnStatementInSelectedCode(model)) + private (ITypeSymbol returnType, bool awaitTaskReturn) WrapReturnTypeInTask(ITypeSymbol returnType) { - // check whether we will use return type as it is or not. - awaitTaskReturn = returnType.Equals(taskType); - return; - } + var compilation = this.SemanticModel.Compilation; + var taskType = compilation.TaskType(); + + // convert void to Task type + if (taskType is object && returnType.Equals(compilation.GetSpecialType(SpecialType.System_Void))) + return (taskType, awaitTaskReturn: true); - var genericTaskType = model.Compilation.TaskOfTType(); + if (!SelectionResult.IsExtractMethodOnExpression && ContainsReturnStatementInSelectedCode()) + return (returnType, awaitTaskReturn: false); + + var genericTaskType = compilation.TaskOfTType(); - if (genericTaskType is object) - { // okay, wrap the return type in Task - returnType = genericTaskType.Construct(returnType); + if (genericTaskType is object) + returnType = genericTaskType.Construct(returnType); + + return (returnType, awaitTaskReturn: false); } - } - private (ImmutableArray parameters, ITypeSymbol returnType, bool returnsByRef, ImmutableArray variablesToUseAsReturnValue, bool unsafeAddressTakenUsed) - GetSignatureInformation( - DataFlowAnalysis dataFlowAnalysisData, - Dictionary variableInfoMap, - bool isInExpressionOrHasReturnStatement) - { - var model = _semanticDocument.SemanticModel; - var compilation = model.Compilation; - if (isInExpressionOrHasReturnStatement) + private (ImmutableArray parameters, ITypeSymbol returnType, bool returnsByRef, ImmutableArray variablesToUseAsReturnValue) + GetSignatureInformation(Dictionary variableInfoMap, bool isInExpressionOrHasReturnStatement) { - // check whether current selection contains return statement - var parameters = GetMethodParameters(variableInfoMap); - var (returnType, returnsByRef) = SelectionResult.GetReturnType(); - returnType ??= compilation.GetSpecialType(SpecialType.System_Object); + var model = this.SemanticDocument.SemanticModel; + var compilation = model.Compilation; + if (isInExpressionOrHasReturnStatement) + { + // check whether current selection contains return statement + var parameters = GetMethodParameters(variableInfoMap); + var (returnType, returnsByRef) = SelectionResult.GetReturnType(); + returnType ??= compilation.GetSpecialType(SpecialType.System_Object); - var unsafeAddressTakenUsed = ContainsVariableUnsafeAddressTaken(dataFlowAnalysisData, variableInfoMap.Keys); - return (parameters, returnType, returnsByRef, [], unsafeAddressTakenUsed); - } - else - { - // no return statement - var parameters = MarkVariableInfosToUseAsReturnValueIfPossible(GetMethodParameters(variableInfoMap)); - var variablesToUseAsReturnValue = parameters.WhereAsArray(v => v.UseAsReturnValue); + return (parameters, returnType, returnsByRef, []); + } + else + { + // no return statement + var parameters = MarkVariableInfosToUseAsReturnValueIfPossible(GetMethodParameters(variableInfoMap)); + var variablesToUseAsReturnValue = parameters.WhereAsArray(v => v.UseAsReturnValue); - var returnType = GetReturnType(variablesToUseAsReturnValue); + var returnType = GetReturnType(variablesToUseAsReturnValue); - var unsafeAddressTakenUsed = ContainsVariableUnsafeAddressTaken(dataFlowAnalysisData, variableInfoMap.Keys); - return (parameters, returnType, returnsByRef: false, variablesToUseAsReturnValue, unsafeAddressTakenUsed); - } + return (parameters, returnType, returnsByRef: false, variablesToUseAsReturnValue); + } - ITypeSymbol GetReturnType(ImmutableArray variablesToUseAsReturnValue) - { - if (variablesToUseAsReturnValue.IsEmpty) - return compilation.GetSpecialType(SpecialType.System_Void); + ITypeSymbol GetReturnType(ImmutableArray variablesToUseAsReturnValue) + { + if (variablesToUseAsReturnValue.IsEmpty) + return compilation.GetSpecialType(SpecialType.System_Void); - if (variablesToUseAsReturnValue is [var info]) - return info.GetVariableType(); + if (variablesToUseAsReturnValue is [var info]) + return info.GetVariableType(); - return compilation.CreateTupleTypeSymbol( - variablesToUseAsReturnValue.SelectAsArray(v => v.GetVariableType()), - variablesToUseAsReturnValue.SelectAsArray(v => v.Name)!); + return compilation.CreateTupleTypeSymbol( + variablesToUseAsReturnValue.SelectAsArray(v => v.GetVariableType()), + variablesToUseAsReturnValue.SelectAsArray(v => v.Name)!); + } } - } - private bool IsInExpressionOrHasReturnStatement(SemanticModel model) - { - var isInExpressionOrHasReturnStatement = SelectionResult.SelectionInExpression; - if (!isInExpressionOrHasReturnStatement) + private bool IsInExpressionOrHasReturnStatement() { - var containsReturnStatement = ContainsReturnStatementInSelectedCode(model); - isInExpressionOrHasReturnStatement |= containsReturnStatement; + var isInExpressionOrHasReturnStatement = SelectionResult.IsExtractMethodOnExpression; + if (!isInExpressionOrHasReturnStatement) + { + var containsReturnStatement = ContainsReturnStatementInSelectedCode(); + isInExpressionOrHasReturnStatement |= containsReturnStatement; + } + + return isInExpressionOrHasReturnStatement; } - return isInExpressionOrHasReturnStatement; - } + private OperationStatus GetOperationStatus( + Dictionary> symbolMap, + IList parameters, + IList failedVariables, + bool unsafeAddressTakenUsed, + bool returnTypeHasAnonymousType, + bool containsAnyLocalFunctionCallNotWithinSpan) + { + var readonlyFieldStatus = CheckReadOnlyFields(symbolMap); - private OperationStatus GetOperationStatus( - SemanticModel model, - Dictionary> symbolMap, - IList parameters, - IList failedVariables, - bool unsafeAddressTakenUsed, - bool returnTypeHasAnonymousType, - bool containsAnyLocalFunctionCallNotWithinSpan) - { - var readonlyFieldStatus = CheckReadOnlyFields(model, symbolMap); + var namesWithAnonymousTypes = parameters.Where(v => v.OriginalTypeHadAnonymousTypeOrDelegate).Select(v => v.Name ?? string.Empty); + if (returnTypeHasAnonymousType) + { + namesWithAnonymousTypes = namesWithAnonymousTypes.Concat("return type"); + } - var namesWithAnonymousTypes = parameters.Where(v => v.OriginalTypeHadAnonymousTypeOrDelegate).Select(v => v.Name ?? string.Empty); - if (returnTypeHasAnonymousType) - { - namesWithAnonymousTypes = namesWithAnonymousTypes.Concat("return type"); - } + var anonymousTypeStatus = !namesWithAnonymousTypes.Any() + ? OperationStatus.SucceededStatus + : new OperationStatus(succeeded: true, + string.Format( + FeaturesResources.Parameters_type_or_return_type_cannot_be_an_anonymous_type_colon_bracket_0_bracket, + string.Join(", ", namesWithAnonymousTypes))); - var anonymousTypeStatus = !namesWithAnonymousTypes.Any() - ? OperationStatus.SucceededStatus - : new OperationStatus(succeeded: true, - string.Format( - FeaturesResources.Parameters_type_or_return_type_cannot_be_an_anonymous_type_colon_bracket_0_bracket, - string.Join(", ", namesWithAnonymousTypes))); - - var unsafeAddressStatus = unsafeAddressTakenUsed - ? OperationStatus.UnsafeAddressTaken - : OperationStatus.SucceededStatus; - - var asyncRefOutParameterStatus = CheckAsyncMethodRefOutParameters(parameters); - - var variableMapStatus = failedVariables.Count == 0 - ? OperationStatus.SucceededStatus - : new OperationStatus(succeeded: true, - string.Format( - FeaturesResources.Failed_to_analyze_data_flow_for_0, - string.Join(", ", failedVariables.Select(v => v.Name)))); - - var localFunctionStatus = (containsAnyLocalFunctionCallNotWithinSpan && !LocalFunction) - ? OperationStatus.LocalFunctionCallWithoutDeclaration - : OperationStatus.SucceededStatus; - - return readonlyFieldStatus.With(anonymousTypeStatus) - .With(unsafeAddressStatus) - .With(asyncRefOutParameterStatus) - .With(variableMapStatus) - .With(localFunctionStatus); - } + var unsafeAddressStatus = unsafeAddressTakenUsed + ? OperationStatus.UnsafeAddressTaken + : OperationStatus.SucceededStatus; - private OperationStatus CheckAsyncMethodRefOutParameters(IList parameters) - { - if (SelectionResult.CreateAsyncMethod()) - { - var names = parameters.Where(v => v is { UseAsReturnValue: false, ParameterModifier: ParameterBehavior.Out or ParameterBehavior.Ref }) - .Select(p => p.Name ?? string.Empty); + var asyncRefOutParameterStatus = CheckAsyncMethodRefOutParameters(parameters); - if (names.Any()) - return new OperationStatus(succeeded: true, string.Format(FeaturesResources.Asynchronous_method_cannot_have_ref_out_parameters_colon_bracket_0_bracket, string.Join(", ", names))); - } + var variableMapStatus = failedVariables.Count == 0 + ? OperationStatus.SucceededStatus + : new OperationStatus(succeeded: true, + string.Format( + FeaturesResources.Failed_to_analyze_data_flow_for_0, + string.Join(", ", failedVariables.Select(v => v.Name)))); - return OperationStatus.SucceededStatus; - } + var localFunctionStatus = (containsAnyLocalFunctionCallNotWithinSpan && !LocalFunction) + ? OperationStatus.LocalFunctionCallWithoutDeclaration + : OperationStatus.SucceededStatus; - private Dictionary> GetSymbolMap(SemanticModel model) - { - var context = SelectionResult.GetContainingScope(); - var symbolMap = SymbolMapBuilder.Build(this.SyntaxFacts, model, context, SelectionResult.FinalSpan, CancellationToken); - return symbolMap; - } + return readonlyFieldStatus.With(anonymousTypeStatus) + .With(unsafeAddressStatus) + .With(asyncRefOutParameterStatus) + .With(variableMapStatus) + .With(localFunctionStatus); + } - private static bool ContainsVariableUnsafeAddressTaken(DataFlowAnalysis dataFlowAnalysisData, IEnumerable symbols) - { - // check whether the selection contains "&" over a symbol exist - var map = new HashSet(dataFlowAnalysisData.UnsafeAddressTaken); - return symbols.Any(map.Contains); - } + private OperationStatus CheckAsyncMethodRefOutParameters(IList parameters) + { + if (SelectionResult.CreateAsyncMethod()) + { + var names = parameters.Where(v => v is { UseAsReturnValue: false, ParameterModifier: ParameterBehavior.Out or ParameterBehavior.Ref }) + .Select(p => p.Name ?? string.Empty); - private DataFlowAnalysis GetDataFlowAnalysisData(SemanticModel model) - { - if (SelectionResult.SelectionInExpression) - return model.AnalyzeDataFlow(SelectionResult.GetNodeForDataFlowAnalysis()); + if (names.Any()) + return new OperationStatus(succeeded: true, string.Format(FeaturesResources.Asynchronous_method_cannot_have_ref_out_parameters_colon_bracket_0_bracket, string.Join(", ", names))); + } - var (firstStatement, lastStatement) = GetFlowAnalysisNodeRange(); - return model.AnalyzeDataFlow(firstStatement, lastStatement); - } + return OperationStatus.SucceededStatus; + } - private bool IsEndOfSelectionReachable(SemanticModel model) - { - if (SelectionResult.SelectionInExpression) + private Dictionary> GetSymbolMap() { - return true; + var context = SelectionResult.GetContainingScope(); + var symbolMap = SymbolMapBuilder.Build(this.SyntaxFacts, this.SemanticModel, context, SelectionResult.FinalSpan, CancellationToken); + return symbolMap; } - var (firstStatement, lastStatement) = GetFlowAnalysisNodeRange(); - var analysis = model.AnalyzeControlFlow(firstStatement, lastStatement); - return analysis.EndPointIsReachable; - } + private DataFlowAnalysis GetDataFlowAnalysisData() + { + if (SelectionResult.IsExtractMethodOnExpression) + return this.SemanticModel.AnalyzeDataFlow(SelectionResult.GetNodeForDataFlowAnalysis()); - private ImmutableArray MarkVariableInfosToUseAsReturnValueIfPossible(ImmutableArray variableInfo) - { - var index = GetIndexOfVariableInfoToUseAsReturnValue(variableInfo, out var numberOfOutParameters, out var numberOfRefParameters); - - // If there are any variables we'd make out/ref and this is async, then we need to make these the - // return values of the method since we can't actually have out/ref with an async method. - var outRefCount = numberOfOutParameters + numberOfRefParameters; - if (outRefCount > 0 && - this.SelectionResult.CreateAsyncMethod() && - this.SyntaxFacts.SupportsTupleDeconstruction(_semanticDocument.Document.Project.ParseOptions!)) + var (firstStatement, lastStatement) = this.SelectionResult.GetFlowAnalysisNodeRange(); + return this.SemanticModel.AnalyzeDataFlow(firstStatement, lastStatement); + } + + private bool IsEndOfSelectionReachable() { - var result = new FixedSizeArrayBuilder(variableInfo.Length); - foreach (var info in variableInfo) + if (SelectionResult.IsExtractMethodOnExpression) { - result.Add(info.CanBeUsedAsReturnValue && info.ParameterModifier is ParameterBehavior.Out or ParameterBehavior.Ref - ? VariableInfo.CreateReturnValue(info) - : info); + return true; } - return result.MoveToImmutable(); + var (firstStatement, lastStatement) = this.SelectionResult.GetFlowAnalysisNodeRange(); + var analysis = this.SemanticModel.AnalyzeControlFlow(firstStatement, lastStatement); + return analysis.EndPointIsReachable; } - // If there's just one variable that would be ref/out, then make that the return value of the final method. - if (index >= 0) - return variableInfo.SetItem(index, VariableInfo.CreateReturnValue(variableInfo[index])); + private ImmutableArray MarkVariableInfosToUseAsReturnValueIfPossible(ImmutableArray variableInfo) + { + var index = GetIndexOfVariableInfoToUseAsReturnValue(variableInfo, out var numberOfOutParameters, out var numberOfRefParameters); - return variableInfo; - } + // If there are any variables we'd make out/ref and this is async, then we need to make these the + // return values of the method since we can't actually have out/ref with an async method. + var outRefCount = numberOfOutParameters + numberOfRefParameters; + if (outRefCount > 0 && + this.SelectionResult.CreateAsyncMethod() && + this.SyntaxFacts.SupportsTupleDeconstruction(this.SemanticDocument.Document.Project.ParseOptions!)) + { + var result = new FixedSizeArrayBuilder(variableInfo.Length); + foreach (var info in variableInfo) + { + result.Add(info.CanBeUsedAsReturnValue && info.ParameterModifier is ParameterBehavior.Out or ParameterBehavior.Ref + ? VariableInfo.CreateReturnValue(info) + : info); + } - /// - /// among variables that will be used as parameters at the extracted method, check whether one of the parameter can be used as return - /// - private int GetIndexOfVariableInfoToUseAsReturnValue( - ImmutableArray variableInfo, - out int numberOfOutParameters, - out int numberOfRefParameters) - { - numberOfOutParameters = 0; - numberOfRefParameters = 0; + return result.MoveToImmutable(); + } - var outSymbolIndex = -1; - var refSymbolIndex = -1; + // If there's just one variable that would be ref/out, then make that the return value of the final method. + if (index >= 0) + return variableInfo.SetItem(index, VariableInfo.CreateReturnValue(variableInfo[index])); - for (var i = 0; i < variableInfo.Length; i++) + return variableInfo; + } + + /// + /// among variables that will be used as parameters at the extracted method, check whether one of the parameter can be used as return + /// + private int GetIndexOfVariableInfoToUseAsReturnValue( + ImmutableArray variableInfo, + out int numberOfOutParameters, + out int numberOfRefParameters) { - var variable = variableInfo[i]; + numberOfOutParameters = 0; + numberOfRefParameters = 0; - // there should be no-one set as return value yet - Contract.ThrowIfTrue(variable.UseAsReturnValue); + var outSymbolIndex = -1; + var refSymbolIndex = -1; - if (!variable.CanBeUsedAsReturnValue) + for (var i = 0; i < variableInfo.Length; i++) { - continue; + var variable = variableInfo[i]; + + // there should be no-one set as return value yet + Contract.ThrowIfTrue(variable.UseAsReturnValue); + + if (!variable.CanBeUsedAsReturnValue) + { + continue; + } + + // check modifier + if (variable.ParameterModifier == ParameterBehavior.Ref || + (variable.ParameterModifier == ParameterBehavior.Out && TreatOutAsRef)) + { + numberOfRefParameters++; + refSymbolIndex = i; + } + else if (variable.ParameterModifier == ParameterBehavior.Out) + { + numberOfOutParameters++; + outSymbolIndex = i; + } } - // check modifier - if (variable.ParameterModifier == ParameterBehavior.Ref || - (variable.ParameterModifier == ParameterBehavior.Out && TreatOutAsRef)) + // if there is only one "out" or "ref", that will be converted to return statement. + if (numberOfOutParameters == 1) { - numberOfRefParameters++; - refSymbolIndex = i; + return outSymbolIndex; } - else if (variable.ParameterModifier == ParameterBehavior.Out) + + if (numberOfRefParameters == 1) { - numberOfOutParameters++; - outSymbolIndex = i; + return refSymbolIndex; } - } - // if there is only one "out" or "ref", that will be converted to return statement. - if (numberOfOutParameters == 1) - { - return outSymbolIndex; + return -1; } - if (numberOfRefParameters == 1) + private static ImmutableArray GetMethodParameters(Dictionary variableInfoMap) { - return refSymbolIndex; + var list = new FixedSizeArrayBuilder(variableInfoMap.Count); + list.AddRange(variableInfoMap.Values); + list.Sort(); + return list.MoveToImmutable(); } - return -1; - } + /// When false, variables whose data flow is not understood + /// will be returned in . When true, we assume any + /// variable we don't understand has + private void GenerateVariableInfoMap( + bool bestEffort, + DataFlowAnalysis dataFlowAnalysisData, + Dictionary> symbolMap, + bool isInPrimaryConstructorBaseType, + out Dictionary variableInfoMap, + out List failedVariables) + { + Contract.ThrowIfNull(dataFlowAnalysisData); + + variableInfoMap = []; + failedVariables = []; + + // create map of each data + using var _0 = GetPooledSymbolSet(dataFlowAnalysisData.Captured, out var capturedMap); + using var _1 = GetPooledSymbolSet(dataFlowAnalysisData.DataFlowsIn, out var dataFlowInMap); + using var _2 = GetPooledSymbolSet(dataFlowAnalysisData.DataFlowsOut, out var dataFlowOutMap); + using var _3 = GetPooledSymbolSet(dataFlowAnalysisData.AlwaysAssigned, out var alwaysAssignedMap); + using var _4 = GetPooledSymbolSet(dataFlowAnalysisData.VariablesDeclared, out var variableDeclaredMap); + using var _5 = GetPooledSymbolSet(dataFlowAnalysisData.ReadInside, out var readInsideMap); + using var _6 = GetPooledSymbolSet(dataFlowAnalysisData.WrittenInside, out var writtenInsideMap); + using var _7 = GetPooledSymbolSet(dataFlowAnalysisData.ReadOutside, out var readOutsideMap); + using var _8 = GetPooledSymbolSet(dataFlowAnalysisData.WrittenOutside, out var writtenOutsideMap); + using var _9 = GetPooledSymbolSet(dataFlowAnalysisData.UnsafeAddressTaken, out var unsafeAddressTakenMap); + + // gather all meaningful symbols for the span. + var candidates = new HashSet(readInsideMap); + candidates.UnionWith(writtenInsideMap); + candidates.UnionWith(variableDeclaredMap); + + // Need to analyze from the start of what we're extracting to the end of the scope that this variable could + // have been referenced in. + var containingScope = SelectionResult.GetContainingScope(); + var analysisRange = TextSpan.FromBounds(SelectionResult.FinalSpan.Start, containingScope.Span.End); + var selectionOperation = this.SemanticModel.GetOperation(containingScope); + + foreach (var symbol in candidates) + { + // We don't care about the 'this' parameter. It will be available to an extracted method already. + if (symbol.IsThisParameter()) + continue; - private static ImmutableArray GetMethodParameters(Dictionary variableInfoMap) - { - var list = new FixedSizeArrayBuilder(variableInfoMap.Count); - list.AddRange(variableInfoMap.Values); - list.Sort(); - return list.MoveToImmutable(); - } + // Primary constructor parameters will be in scope for any instance extracted method. No need to do + // anything special with them. They won't be in scope for a static method generated in a primary + // constructor base type list. + if (!isInPrimaryConstructorBaseType && symbol is IParameterSymbol parameter && parameter.IsPrimaryConstructor(this.CancellationToken)) + continue; - /// When false, variables whose data flow is not understood - /// will be returned in . When true, we assume any - /// variable we don't understand has - private void GenerateVariableInfoMap( - bool bestEffort, - SemanticModel semanticModel, - DataFlowAnalysis dataFlowAnalysisData, - Dictionary> symbolMap, - bool isInPrimaryConstructorBaseType, - out Dictionary variableInfoMap, - out List failedVariables) - { - Contract.ThrowIfNull(semanticModel); - Contract.ThrowIfNull(dataFlowAnalysisData); - - variableInfoMap = []; - failedVariables = []; - - // create map of each data - using var _0 = GetPooledSymbolSet(dataFlowAnalysisData.Captured, out var capturedMap); - using var _1 = GetPooledSymbolSet(dataFlowAnalysisData.DataFlowsIn, out var dataFlowInMap); - using var _2 = GetPooledSymbolSet(dataFlowAnalysisData.DataFlowsOut, out var dataFlowOutMap); - using var _3 = GetPooledSymbolSet(dataFlowAnalysisData.AlwaysAssigned, out var alwaysAssignedMap); - using var _4 = GetPooledSymbolSet(dataFlowAnalysisData.VariablesDeclared, out var variableDeclaredMap); - using var _5 = GetPooledSymbolSet(dataFlowAnalysisData.ReadInside, out var readInsideMap); - using var _6 = GetPooledSymbolSet(dataFlowAnalysisData.WrittenInside, out var writtenInsideMap); - using var _7 = GetPooledSymbolSet(dataFlowAnalysisData.ReadOutside, out var readOutsideMap); - using var _8 = GetPooledSymbolSet(dataFlowAnalysisData.WrittenOutside, out var writtenOutsideMap); - using var _9 = GetPooledSymbolSet(dataFlowAnalysisData.UnsafeAddressTaken, out var unsafeAddressTakenMap); - - // gather all meaningful symbols for the span. - var candidates = new HashSet(readInsideMap); - candidates.UnionWith(writtenInsideMap); - candidates.UnionWith(variableDeclaredMap); - - // Need to analyze from the start of what we're extracting to the end of the scope that this variable could - // have been referenced in. - var analysisRange = TextSpan.FromBounds(SelectionResult.FinalSpan.Start, SelectionResult.GetContainingScope().Span.End); - var selectionOperation = semanticModel.GetOperation(SelectionResult.GetContainingScope()); - - foreach (var symbol in candidates) - { - // We don't care about the 'this' parameter. It will be available to an extracted method already. - if (symbol.IsThisParameter()) - continue; - - // Primary constructor parameters will be in scope for any instance extracted method. No need to do - // anything special with them. They won't be in scope for a static method generated in a primary - // constructor base type list. - if (!isInPrimaryConstructorBaseType && symbol is IParameterSymbol parameter && parameter.IsPrimaryConstructor(this.CancellationToken)) - continue; - - if (IsInteractiveSynthesizedParameter(symbol)) - continue; - - var captured = capturedMap.Contains(symbol); - var dataFlowIn = dataFlowInMap.Contains(symbol); - var dataFlowOut = dataFlowOutMap.Contains(symbol); - var alwaysAssigned = alwaysAssignedMap.Contains(symbol); - var variableDeclared = variableDeclaredMap.Contains(symbol); - var readInside = readInsideMap.Contains(symbol); - var writtenInside = writtenInsideMap.Contains(symbol); - var readOutside = IsReadOutside(symbol, readOutsideMap); - var writtenOutside = writtenOutsideMap.Contains(symbol); - var unsafeAddressTaken = unsafeAddressTakenMap.Contains(symbol); - - // if it is static local, make sure it is not defined inside - if (symbol.IsStatic) - { - dataFlowIn = dataFlowIn && !variableDeclared; - } + if (IsInteractiveSynthesizedParameter(symbol)) + continue; - // make sure readoutside is true when dataflowout is true (bug #3790) - // when a variable is only used inside of loop, a situation where dataflowout == true and readOutside == false - // can happen. but for extract method's point of view, this is not an information that would affect output. - // so, here we adjust flags to follow predefined assumption. - readOutside = readOutside || dataFlowOut; + var captured = capturedMap.Contains(symbol); + var dataFlowIn = dataFlowInMap.Contains(symbol); + var dataFlowOut = dataFlowOutMap.Contains(symbol); + var alwaysAssigned = alwaysAssignedMap.Contains(symbol); + var variableDeclared = variableDeclaredMap.Contains(symbol); + var readInside = readInsideMap.Contains(symbol); + var writtenInside = writtenInsideMap.Contains(symbol); + var readOutside = IsReadOutside(symbol, readOutsideMap); + var writtenOutside = writtenOutsideMap.Contains(symbol); + var unsafeAddressTaken = unsafeAddressTakenMap.Contains(symbol); + + // if it is static local, make sure it is not defined inside + if (symbol.IsStatic) + { + dataFlowIn = dataFlowIn && !variableDeclared; + } - // make sure data flow out is true when declared inside/written inside/read outside/not written outside are true (bug #6277) - dataFlowOut = dataFlowOut || (variableDeclared && writtenInside && readOutside && !writtenOutside); + // make sure readoutside is true when dataflowout is true (bug #3790) + // when a variable is only used inside of loop, a situation where dataflowout == true and readOutside == false + // can happen. but for extract method's point of view, this is not an information that would affect output. + // so, here we adjust flags to follow predefined assumption. + readOutside = readOutside || dataFlowOut; - // variable that is declared inside but never referenced outside. just ignore it and move to next one. - if (variableDeclared && !dataFlowOut && !readOutside && !writtenOutside) - continue; + // make sure data flow out is true when declared inside/written inside/read outside/not written outside are true (bug #6277) + dataFlowOut = dataFlowOut || (variableDeclared && writtenInside && readOutside && !writtenOutside); - // parameter defined inside of the selection (such as lambda parameter) will be ignored (bug # 10964) - if (symbol is IParameterSymbol && variableDeclared) - continue; + // variable that is declared inside but never referenced outside. just ignore it and move to next one. + if (variableDeclared && !dataFlowOut && !readOutside && !writtenOutside) + continue; - var type = GetSymbolType(symbol); - if (type == null) - continue; + // parameter defined inside of the selection (such as lambda parameter) will be ignored (bug # 10964) + if (symbol is IParameterSymbol && variableDeclared) + continue; - // If the variable doesn't have a name, it is invalid. - if (symbol.Name.IsEmpty()) - continue; + var type = GetSymbolType(symbol); + if (type == null) + continue; - if (!TryGetVariableStyle( - bestEffort, symbolMap, symbol, semanticModel, type, - captured, dataFlowIn, dataFlowOut, alwaysAssigned, variableDeclared, - readInside, writtenInside, readOutside, writtenOutside, unsafeAddressTaken, - out var variableStyle)) - { - Contract.ThrowIfTrue(bestEffort, "Should never fail if bestEffort is true"); - failedVariables.Add(symbol); - continue; - } + // If the variable doesn't have a name, it is invalid. + if (symbol.Name.IsEmpty()) + continue; - AddVariableToMap( - variableInfoMap, - symbol, - CreateFromSymbol(symbol, type, variableStyle, variableDeclared)); - } + if (!TryGetVariableStyle( + bestEffort, symbolMap, symbol, type, + captured, dataFlowIn, dataFlowOut, alwaysAssigned, variableDeclared, + readInside, writtenInside, readOutside, writtenOutside, unsafeAddressTaken, + out var variableStyle)) + { + Contract.ThrowIfTrue(bestEffort, "Should never fail if bestEffort is true"); + failedVariables.Add(symbol); + continue; + } - return; + AddVariableToMap( + variableInfoMap, + symbol, + CreateFromSymbol(symbol, type, variableStyle, variableDeclared)); + } - PooledDisposer> GetPooledSymbolSet(ImmutableArray symbols, out PooledHashSet symbolSet) - { - var disposer = PooledHashSet.GetInstance(out symbolSet); - symbolSet.AddRange(symbols); - return disposer; - } + return; - ITypeSymbol? GetSymbolType(ISymbol symbol) - { - var type = symbol switch + PooledDisposer> GetPooledSymbolSet(ImmutableArray symbols, out PooledHashSet symbolSet) { - ILocalSymbol local => local.Type, - IParameterSymbol parameter => parameter.Type, - IRangeVariableSymbol rangeVariable => GetRangeVariableType(semanticModel, rangeVariable), - _ => throw ExceptionUtilities.UnexpectedValue(symbol) - }; - - if (type is null) - return type; + var disposer = PooledHashSet.GetInstance(out symbolSet); + symbolSet.AddRange(symbols); + return disposer; + } - // Check if null is possibly assigned to the symbol. If it is, leave nullable annotation as is, otherwise we - // can modify the annotation to be NotAnnotated to code that more likely matches the user's intent. + ITypeSymbol? GetSymbolType(ISymbol symbol) + { + var type = symbol switch + { + ILocalSymbol local => local.Type, + IParameterSymbol parameter => parameter.Type, + IRangeVariableSymbol rangeVariable => GetRangeVariableType(rangeVariable), + _ => throw ExceptionUtilities.UnexpectedValue(symbol) + }; + + if (type is null) + return type; + + // Check if null is possibly assigned to the symbol. If it is, leave nullable annotation as is, otherwise we + // can modify the annotation to be NotAnnotated to code that more likely matches the user's intent. + + if (type.NullableAnnotation is not NullableAnnotation.Annotated) + return type; + + // For Extract-Method we don't care about analyzing the declaration of this variable. For example, even if + // it was initially assigned 'null' for the purposes of determining the type of it for a return value, all + // we care is if it is null at the end of the selection. If it is only assigned non-null values, for + // example, we want to treat it as non-null. + if (selectionOperation is not null && + NullableHelpers.IsSymbolAssignedPossiblyNullValue( + this.SemanticFacts, this.SemanticModel, selectionOperation, symbol, analysisRange, includeDeclaration: false, this.CancellationToken) == false) + { + return type.WithNullableAnnotation(NullableAnnotation.NotAnnotated); + } - if (type.NullableAnnotation is not NullableAnnotation.Annotated) return type; + } + } - // For Extract-Method we don't care about analyzing the declaration of this variable. For example, even if - // it was initially assigned 'null' for the purposes of determining the type of it for a return value, all - // we care is if it is null at the end of the selection. If it is only assigned non-null values, for - // example, we want to treat it as non-null. - if (selectionOperation is not null && - NullableHelpers.IsSymbolAssignedPossiblyNullValue( - this.SemanticFacts, semanticModel, selectionOperation, symbol, analysisRange, includeDeclaration: false, this.CancellationToken) == false) + private static void AddVariableToMap(IDictionary variableInfoMap, ISymbol localOrParameter, VariableInfo variableInfo) + => variableInfoMap.Add(localOrParameter, variableInfo); + + private bool TryGetVariableStyle( + bool bestEffort, + Dictionary> symbolMap, + ISymbol symbol, + ITypeSymbol type, + bool captured, + bool dataFlowIn, + bool dataFlowOut, + bool alwaysAssigned, + bool variableDeclared, + bool readInside, + bool writtenInside, + bool readOutside, + bool writtenOutside, + bool unsafeAddressTaken, + out VariableStyle variableStyle) + { + Contract.ThrowIfNull(type); + + if (!ExtractMethodMatrix.TryGetVariableStyle( + bestEffort, dataFlowIn, dataFlowOut, alwaysAssigned, variableDeclared, + readInside, writtenInside, readOutside, writtenOutside, unsafeAddressTaken, + out variableStyle)) { - return type.WithNullableAnnotation(NullableAnnotation.NotAnnotated); + Contract.ThrowIfTrue(bestEffort, "Should never fail if bestEffort is true"); + return false; } - return type; - } - } + if (SelectionContainsOnlyIdentifierWithSameType(type)) + { + return true; + } - private static void AddVariableToMap(IDictionary variableInfoMap, ISymbol localOrParameter, VariableInfo variableInfo) - => variableInfoMap.Add(localOrParameter, variableInfo); - - private bool TryGetVariableStyle( - bool bestEffort, - Dictionary> symbolMap, - ISymbol symbol, - SemanticModel model, - ITypeSymbol type, - bool captured, - bool dataFlowIn, - bool dataFlowOut, - bool alwaysAssigned, - bool variableDeclared, - bool readInside, - bool writtenInside, - bool readOutside, - bool writtenOutside, - bool unsafeAddressTaken, - out VariableStyle variableStyle) - { - Contract.ThrowIfNull(model); - Contract.ThrowIfNull(type); + // for captured variable, never try to move the decl into extracted method + if (captured && variableStyle == VariableStyle.MoveIn) + { + variableStyle = VariableStyle.Out; + return true; + } - if (!ExtractMethodMatrix.TryGetVariableStyle( - bestEffort, dataFlowIn, dataFlowOut, alwaysAssigned, variableDeclared, - readInside, writtenInside, readOutside, writtenOutside, unsafeAddressTaken, - out variableStyle)) - { - Contract.ThrowIfTrue(bestEffort, "Should never fail if bestEffort is true"); - return false; - } + // check special value type cases + if (type.IsValueType && !IsWrittenInsideForFrameworkValueType(symbolMap, symbol, writtenInside)) + { + return true; + } - if (SelectionContainsOnlyIdentifierWithSameType(type)) - { - return true; - } + // don't blindly always return. make sure there is a write inside of the selection + if (!writtenInside) + { + return true; + } - // for captured variable, never try to move the decl into extracted method - if (captured && variableStyle == VariableStyle.MoveIn) - { - variableStyle = VariableStyle.Out; + variableStyle = AlwaysReturn(variableStyle); return true; } - // check special value type cases - if (type.IsValueType && !IsWrittenInsideForFrameworkValueType(symbolMap, model, symbol, writtenInside)) + private bool IsWrittenInsideForFrameworkValueType( + Dictionary> symbolMap, ISymbol symbol, bool writtenInside) { - return true; + if (!symbolMap.TryGetValue(symbol, out var tokens)) + return writtenInside; + + var semanticFacts = this.SemanticFacts; + return tokens.Any(t => semanticFacts.IsWrittenTo(this.SemanticModel, t.Parent, this.CancellationToken)); } - // don't blindly always return. make sure there is a write inside of the selection - if (!writtenInside) + private bool SelectionContainsOnlyIdentifierWithSameType(ITypeSymbol type) { - return true; - } + if (!SelectionResult.IsExtractMethodOnExpression) + { + return false; + } - variableStyle = AlwaysReturn(variableStyle); - return true; - } + var firstToken = SelectionResult.GetFirstTokenInSelection(); + var lastToken = SelectionResult.GetLastTokenInSelection(); - private bool IsWrittenInsideForFrameworkValueType( - Dictionary> symbolMap, SemanticModel model, ISymbol symbol, bool writtenInside) - { - if (!symbolMap.TryGetValue(symbol, out var tokens)) - return writtenInside; + if (!firstToken.Equals(lastToken)) + { + return false; + } - var semanticFacts = this.SemanticFacts; - return tokens.Any(t => semanticFacts.IsWrittenTo(model, t.Parent, this.CancellationToken)); - } + return type.Equals(SelectionResult.GetContainingScopeType()); + } - private bool SelectionContainsOnlyIdentifierWithSameType(ITypeSymbol type) - { - if (!SelectionResult.SelectionInExpression) + protected static VariableStyle AlwaysReturn(VariableStyle style) { - return false; - } + if (style == VariableStyle.InputOnly) + { + return VariableStyle.Ref; + } - var firstToken = SelectionResult.GetFirstTokenInSelection(); - var lastToken = SelectionResult.GetLastTokenInSelection(); + if (style == VariableStyle.MoveIn) + { + return VariableStyle.Out; + } - if (!firstToken.Equals(lastToken)) - { - return false; - } + if (style == VariableStyle.SplitIn) + { + return VariableStyle.Out; + } - return type.Equals(SelectionResult.GetContainingScopeType()); - } + if (style == VariableStyle.SplitOut) + { + return VariableStyle.OutWithMoveOut; + } - protected static VariableStyle AlwaysReturn(VariableStyle style) - { - if (style == VariableStyle.InputOnly) - { - return VariableStyle.Ref; + return style; } - if (style == VariableStyle.MoveIn) + private static bool IsThisParameter(ISymbol localOrParameter) { - return VariableStyle.Out; - } + if (localOrParameter is not IParameterSymbol parameter) + { + return false; + } - if (style == VariableStyle.SplitIn) - { - return VariableStyle.Out; + return parameter.IsThis; } - if (style == VariableStyle.SplitOut) + private static bool IsInteractiveSynthesizedParameter(ISymbol localOrParameter) { - return VariableStyle.OutWithMoveOut; - } - - return style; - } + if (localOrParameter is not IParameterSymbol parameter) + { + return false; + } - private static bool IsThisParameter(ISymbol localOrParameter) - { - if (localOrParameter is not IParameterSymbol parameter) - { - return false; + return parameter.IsImplicitlyDeclared && + parameter.ContainingAssembly.IsInteractive && + parameter.ContainingSymbol != null && + parameter.ContainingSymbol.ContainingType != null && + parameter.ContainingSymbol.ContainingType.IsScriptClass; } - return parameter.IsThis; - } - - private static bool IsInteractiveSynthesizedParameter(ISymbol localOrParameter) - { - if (localOrParameter is not IParameterSymbol parameter) + private bool ContainsReturnStatementInSelectedCode() { - return false; - } - - return parameter.IsImplicitlyDeclared && - parameter.ContainingAssembly.IsInteractive && - parameter.ContainingSymbol != null && - parameter.ContainingSymbol.ContainingType != null && - parameter.ContainingSymbol.ContainingType.IsScriptClass; - } - - private bool ContainsReturnStatementInSelectedCode(SemanticModel model) - { - Contract.ThrowIfTrue(SelectionResult.SelectionInExpression); + Contract.ThrowIfTrue(SelectionResult.IsExtractMethodOnExpression); - var (firstStatement, lastStatement) = GetFlowAnalysisNodeRange(); - var controlFlowAnalysisData = model.AnalyzeControlFlow(firstStatement, lastStatement); + var (firstStatement, lastStatement) = this.SelectionResult.GetFlowAnalysisNodeRange(); + var controlFlowAnalysisData = this.SemanticDocument.SemanticModel.AnalyzeControlFlow(firstStatement, lastStatement); - return ContainsReturnStatementInSelectedCode(controlFlowAnalysisData.ExitPoints); - } + return ContainsReturnStatementInSelectedCode(controlFlowAnalysisData.ExitPoints); + } - private static void AddTypeParametersToMap(IEnumerable typeParameters, IDictionary sortedMap) - { - foreach (var typeParameter in typeParameters) + private static void AddTypeParametersToMap(IEnumerable typeParameters, IDictionary sortedMap) { - AddTypeParameterToMap(typeParameter, sortedMap); + foreach (var typeParameter in typeParameters) + { + AddTypeParameterToMap(typeParameter, sortedMap); + } } - } - private static void AddTypeParameterToMap(ITypeParameterSymbol typeParameter, IDictionary sortedMap) - { - if (typeParameter == null || - typeParameter.DeclaringMethod == null || - sortedMap.ContainsKey(typeParameter.Ordinal)) + private static void AddTypeParameterToMap(ITypeParameterSymbol typeParameter, IDictionary sortedMap) { - return; - } + if (typeParameter == null || + typeParameter.DeclaringMethod == null || + sortedMap.ContainsKey(typeParameter.Ordinal)) + { + return; + } - sortedMap[typeParameter.Ordinal] = typeParameter; - } + sortedMap[typeParameter.Ordinal] = typeParameter; + } - private void AppendMethodTypeVariableFromDataFlowAnalysis( - SemanticModel model, - IDictionary variableInfoMap, - IDictionary sortedMap) - { - foreach (var symbol in variableInfoMap.Keys) + private void AppendMethodTypeVariableFromDataFlowAnalysis( + IDictionary variableInfoMap, + IDictionary sortedMap) { - switch (symbol) + foreach (var symbol in variableInfoMap.Keys) { - case IParameterSymbol parameter: - AddTypeParametersToMap(TypeParameterCollector.Collect(parameter.Type), sortedMap); - continue; + switch (symbol) + { + case IParameterSymbol parameter: + AddTypeParametersToMap(TypeParameterCollector.Collect(parameter.Type), sortedMap); + continue; - case ILocalSymbol local: - AddTypeParametersToMap(TypeParameterCollector.Collect(local.Type), sortedMap); - continue; + case ILocalSymbol local: + AddTypeParametersToMap(TypeParameterCollector.Collect(local.Type), sortedMap); + continue; - case IRangeVariableSymbol rangeVariable: - var type = GetRangeVariableType(model, rangeVariable); - AddTypeParametersToMap(TypeParameterCollector.Collect(type), sortedMap); - continue; + case IRangeVariableSymbol rangeVariable: + var type = GetRangeVariableType(rangeVariable); + AddTypeParametersToMap(TypeParameterCollector.Collect(type), sortedMap); + continue; - default: - throw ExceptionUtilities.UnexpectedValue(symbol); + default: + throw ExceptionUtilities.UnexpectedValue(symbol); + } } } - } - private static void AppendMethodTypeParameterFromConstraint(SortedDictionary sortedMap) - { - var typeParametersInConstraint = new List(); - - // collect all type parameter appears in constraint - foreach (var typeParameter in sortedMap.Values) + private static void AppendMethodTypeParameterFromConstraint(SortedDictionary sortedMap) { - var constraintTypes = typeParameter.ConstraintTypes; - if (constraintTypes.IsDefaultOrEmpty) + var typeParametersInConstraint = new List(); + + // collect all type parameter appears in constraint + foreach (var typeParameter in sortedMap.Values) { - continue; + var constraintTypes = typeParameter.ConstraintTypes; + if (constraintTypes.IsDefaultOrEmpty) + { + continue; + } + + foreach (var type in constraintTypes) + { + // constraint itself is type parameter + typeParametersInConstraint.AddRange(TypeParameterCollector.Collect(type)); + } } - foreach (var type in constraintTypes) + // pick up only valid type parameter and add them to the map + foreach (var typeParameter in typeParametersInConstraint) { - // constraint itself is type parameter - typeParametersInConstraint.AddRange(TypeParameterCollector.Collect(type)); + AddTypeParameterToMap(typeParameter, sortedMap); } } - // pick up only valid type parameter and add them to the map - foreach (var typeParameter in typeParametersInConstraint) - { - AddTypeParameterToMap(typeParameter, sortedMap); - } - } - - private static void AppendMethodTypeParameterUsedDirectly(IDictionary> symbolMap, IDictionary sortedMap) - { - foreach (var pair in symbolMap.Where(p => p.Key.Kind == SymbolKind.TypeParameter)) + private static void AppendMethodTypeParameterUsedDirectly(IDictionary> symbolMap, IDictionary sortedMap) { - var typeParameter = (ITypeParameterSymbol)pair.Key; - if (typeParameter.DeclaringMethod == null || - sortedMap.ContainsKey(typeParameter.Ordinal)) + foreach (var pair in symbolMap.Where(p => p.Key.Kind == SymbolKind.TypeParameter)) { - continue; - } + var typeParameter = (ITypeParameterSymbol)pair.Key; + if (typeParameter.DeclaringMethod == null || + sortedMap.ContainsKey(typeParameter.Ordinal)) + { + continue; + } - sortedMap[typeParameter.Ordinal] = typeParameter; + sortedMap[typeParameter.Ordinal] = typeParameter; + } } - } - private ImmutableArray GetMethodTypeParametersInConstraintList( - SemanticModel model, - IDictionary variableInfoMap, - IDictionary> symbolMap, - SortedDictionary sortedMap) - { - // find starting points - AppendMethodTypeVariableFromDataFlowAnalysis(model, variableInfoMap, sortedMap); - AppendMethodTypeParameterUsedDirectly(symbolMap, sortedMap); - - // recursively dive into constraints to find all constraints needed - AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(sortedMap); + private ImmutableArray GetMethodTypeParametersInConstraintList( + IDictionary variableInfoMap, + IDictionary> symbolMap, + SortedDictionary sortedMap) + { + // find starting points + AppendMethodTypeVariableFromDataFlowAnalysis(variableInfoMap, sortedMap); + AppendMethodTypeParameterUsedDirectly(symbolMap, sortedMap); - return [.. sortedMap.Values]; - } + // recursively dive into constraints to find all constraints needed + AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(sortedMap); - private static void AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(SortedDictionary sortedMap) - { - var visited = new HashSet(); - var candidates = SpecializedCollections.EmptyEnumerable(); + return [.. sortedMap.Values]; + } - // collect all type parameter appears in constraint - foreach (var typeParameter in sortedMap.Values) + private static void AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(SortedDictionary sortedMap) { - var constraintTypes = typeParameter.ConstraintTypes; - if (constraintTypes.IsDefaultOrEmpty) + var visited = new HashSet(); + var candidates = SpecializedCollections.EmptyEnumerable(); + + // collect all type parameter appears in constraint + foreach (var typeParameter in sortedMap.Values) { - continue; + var constraintTypes = typeParameter.ConstraintTypes; + if (constraintTypes.IsDefaultOrEmpty) + { + continue; + } + + foreach (var type in constraintTypes) + { + candidates = candidates.Concat(AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(type, visited)); + } } - foreach (var type in constraintTypes) + // pick up only valid type parameter and add them to the map + foreach (var typeParameter in candidates) { - candidates = candidates.Concat(AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(type, visited)); + AddTypeParameterToMap(typeParameter, sortedMap); } } - // pick up only valid type parameter and add them to the map - foreach (var typeParameter in candidates) + private static IEnumerable AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints( + ITypeSymbol type, HashSet visited) { - AddTypeParameterToMap(typeParameter, sortedMap); - } - } - - private static IEnumerable AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints( - ITypeSymbol type, HashSet visited) - { - if (visited.Contains(type)) - return []; - - visited.Add(type); + if (visited.Contains(type)) + return []; - if (type.OriginalDefinition.Equals(type)) - return []; + visited.Add(type); - if (type is not INamedTypeSymbol constructedType) - return []; + if (type.OriginalDefinition.Equals(type)) + return []; - var parameters = constructedType.GetAllTypeParameters().ToList(); - var arguments = constructedType.GetAllTypeArguments().ToList(); + if (type is not INamedTypeSymbol constructedType) + return []; - Contract.ThrowIfFalse(parameters.Count == arguments.Count); + var parameters = constructedType.GetAllTypeParameters().ToList(); + var arguments = constructedType.GetAllTypeArguments().ToList(); - var typeParameters = new List(); - for (var i = 0; i < parameters.Count; i++) - { - var parameter = parameters[i]; + Contract.ThrowIfFalse(parameters.Count == arguments.Count); - if (arguments[i] is ITypeParameterSymbol argument) + var typeParameters = new List(); + for (var i = 0; i < parameters.Count; i++) { - // no constraint, nothing to do - if (!parameter.HasConstructorConstraint && - !parameter.HasReferenceTypeConstraint && - !parameter.HasValueTypeConstraint && - !parameter.AllowsRefLikeType && - parameter.ConstraintTypes.IsDefaultOrEmpty) + var parameter = parameters[i]; + + if (arguments[i] is ITypeParameterSymbol argument) { + // no constraint, nothing to do + if (!parameter.HasConstructorConstraint && + !parameter.HasReferenceTypeConstraint && + !parameter.HasValueTypeConstraint && + !parameter.AllowsRefLikeType && + parameter.ConstraintTypes.IsDefaultOrEmpty) + { + continue; + } + + typeParameters.Add(argument); continue; } - typeParameters.Add(argument); - continue; - } + if (arguments[i] is not INamedTypeSymbol candidate) + { + continue; + } - if (arguments[i] is not INamedTypeSymbol candidate) - { - continue; + typeParameters.AddRange(AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(candidate, visited)); } - typeParameters.AddRange(AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(candidate, visited)); + return typeParameters; } - return typeParameters; - } - - private static ImmutableArray GetMethodTypeParametersInDeclaration(ITypeSymbol returnType, SortedDictionary sortedMap) - { - // add return type to the map - AddTypeParametersToMap(TypeParameterCollector.Collect(returnType), sortedMap); - - AppendMethodTypeParameterFromConstraint(sortedMap); + private static ImmutableArray GetMethodTypeParametersInDeclaration(ITypeSymbol returnType, SortedDictionary sortedMap) + { + // add return type to the map + AddTypeParametersToMap(TypeParameterCollector.Collect(returnType), sortedMap); - return [.. sortedMap.Values]; - } + AppendMethodTypeParameterFromConstraint(sortedMap); - private OperationStatus CheckReadOnlyFields(SemanticModel semanticModel, Dictionary> symbolMap) - { - if (ReadOnlyFieldAllowed()) - return OperationStatus.SucceededStatus; + return [.. sortedMap.Values]; + } - using var _ = ArrayBuilder.GetInstance(out var names); - var semanticFacts = this.SemanticFacts; - foreach (var pair in symbolMap.Where(p => p.Key.Kind == SymbolKind.Field)) + private OperationStatus CheckReadOnlyFields(Dictionary> symbolMap) { - var field = (IFieldSymbol)pair.Key; - if (!field.IsReadOnly) - continue; + if (ReadOnlyFieldAllowed()) + return OperationStatus.SucceededStatus; - var tokens = pair.Value; - if (tokens.All(t => !semanticFacts.IsWrittenTo(semanticModel, t.Parent, CancellationToken))) - continue; + using var _ = ArrayBuilder.GetInstance(out var names); + var semanticFacts = this.SemanticFacts; + foreach (var pair in symbolMap.Where(p => p.Key.Kind == SymbolKind.Field)) + { + var field = (IFieldSymbol)pair.Key; + if (!field.IsReadOnly) + continue; - names.Add(field.Name ?? string.Empty); - } + var tokens = pair.Value; + if (tokens.All(t => !semanticFacts.IsWrittenTo(this.SemanticModel, t.Parent, CancellationToken))) + continue; - if (names.Count > 0) - return new OperationStatus(succeeded: true, string.Format(FeaturesResources.Assigning_to_readonly_fields_must_be_done_in_a_constructor_colon_bracket_0_bracket, string.Join(", ", names))); + names.Add(field.Name ?? string.Empty); + } - return OperationStatus.SucceededStatus; - } + if (names.Count > 0) + return new OperationStatus(succeeded: true, string.Format(FeaturesResources.Assigning_to_readonly_fields_must_be_done_in_a_constructor_colon_bracket_0_bracket, string.Join(", ", names))); - protected VariableInfo CreateFromSymbolCommon( - ISymbol symbol, - ITypeSymbol type, - VariableStyle style, - HashSet nonNoisySyntaxKindSet) where T : SyntaxNode - { - var semanticModel = _semanticDocument.SemanticModel; - var compilation = semanticModel.Compilation; + return OperationStatus.SucceededStatus; + } - return symbol switch + protected VariableInfo CreateFromSymbolCommon( + ISymbol symbol, + ITypeSymbol type, + VariableStyle style) { - ILocalSymbol local => new VariableInfo( - new LocalVariableSymbol(compilation, local, type, nonNoisySyntaxKindSet), - style), - IParameterSymbol parameter => new VariableInfo(new ParameterVariableSymbol(compilation, parameter, type), style), - IRangeVariableSymbol rangeVariable => new VariableInfo(new QueryVariableSymbol(compilation, rangeVariable, type), style), - _ => throw ExceptionUtilities.UnexpectedValue(symbol) - }; + return symbol switch + { + ILocalSymbol local => new VariableInfo( + new LocalVariableSymbol(local, type, _nonNoisySyntaxKindSet), + style, + useAsReturnValue: false), + IParameterSymbol parameter => new VariableInfo(new ParameterVariableSymbol(parameter, type), style, useAsReturnValue: false), + IRangeVariableSymbol rangeVariable => new VariableInfo(new QueryVariableSymbol(rangeVariable, type), style, useAsReturnValue: false), + _ => throw ExceptionUtilities.UnexpectedValue(symbol) + }; + } } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs index 4e6511ee08270..c25ab232076d3 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs @@ -14,104 +14,113 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - protected sealed class AnalyzerResult( - ImmutableArray typeParametersInDeclaration, - ImmutableArray typeParametersInConstraintList, - ImmutableArray variables, - ImmutableArray variablesToUseAsReturnValue, - ITypeSymbol returnType, - bool returnsByRef, - bool awaitTaskReturn, - bool instanceMemberIsUsed, - bool shouldBeReadOnly, - bool endOfSelectionReachable, - OperationStatus status) + internal abstract partial class MethodExtractor { - public ImmutableArray MethodTypeParametersInDeclaration { get; } = typeParametersInDeclaration; - public ImmutableArray MethodTypeParametersInConstraintList { get; } = typeParametersInConstraintList; - public ImmutableArray VariablesToUseAsReturnValue { get; } = variablesToUseAsReturnValue; + protected sealed class AnalyzerResult( + ImmutableArray typeParametersInDeclaration, + ImmutableArray typeParametersInConstraintList, + ImmutableArray variables, + ImmutableArray variablesToUseAsReturnValue, + ITypeSymbol returnType, + bool returnsByRef, + bool awaitTaskReturn, + bool instanceMemberIsUsed, + bool shouldBeReadOnly, + bool endOfSelectionReachable, + OperationStatus status) + { + public ImmutableArray MethodTypeParametersInDeclaration { get; } = typeParametersInDeclaration; + public ImmutableArray MethodTypeParametersInConstraintList { get; } = typeParametersInConstraintList; + public ImmutableArray VariablesToUseAsReturnValue { get; } = variablesToUseAsReturnValue; - /// - /// used to determine whether static can be used - /// - public bool UseInstanceMember { get; } = instanceMemberIsUsed; + /// + /// used to determine whether static can be used + /// + public bool UseInstanceMember { get; } = instanceMemberIsUsed; - /// - /// Indicates whether the extracted method should have a 'readonly' modifier. - /// - public bool ShouldBeReadOnly { get; } = shouldBeReadOnly; + /// + /// Indicates whether the extracted method should have a 'readonly' modifier. + /// + public bool ShouldBeReadOnly { get; } = shouldBeReadOnly; - /// - /// used to determine whether "return" statement needs to be inserted - /// - public bool EndOfSelectionReachable { get; } = endOfSelectionReachable; + /// + /// used to determine whether "return" statement needs to be inserted + /// + public bool EndOfSelectionReachable { get; } = endOfSelectionReachable; - /// - /// flag to show whether task return type is due to await - /// - public bool AwaitTaskReturn { get; } = awaitTaskReturn; + /// + /// flag to show whether task return type is due to await + /// + public bool AwaitTaskReturn { get; } = awaitTaskReturn; - public ITypeSymbol ReturnType { get; } = returnType; - public bool ReturnsByRef { get; } = returnsByRef; + public ITypeSymbol ReturnType { get; } = returnType; + public bool ReturnsByRef { get; } = returnsByRef; - /// - /// analyzer result operation status - /// - public OperationStatus Status { get; } = status; + /// + /// analyzer result operation status + /// + public OperationStatus Status { get; } = status; - public ImmutableArray Variables { get; } = variables; + public ImmutableArray Variables { get; } = variables; - public bool HasReturnType - { - get + public bool HasReturnType { - return ReturnType.SpecialType != SpecialType.System_Void && !AwaitTaskReturn; + get + { + return ReturnType.SpecialType != SpecialType.System_Void && !AwaitTaskReturn; + } } - } - public IEnumerable MethodParameters - { - get + public IEnumerable MethodParameters { - return Variables.Where(v => v.UseAsParameter); + get + { + return Variables.Where(v => v.UseAsParameter); + } } - } - public ImmutableArray GetVariablesToSplitOrMoveIntoMethodDefinition(CancellationToken cancellationToken) - { - return Variables.WhereAsArray( - v => v.GetDeclarationBehavior(cancellationToken) is DeclarationBehavior.SplitIn or - DeclarationBehavior.MoveIn); - } + public ImmutableArray GetVariablesToSplitOrMoveIntoMethodDefinition(CancellationToken cancellationToken) + { + return Variables.WhereAsArray( + v => v.GetDeclarationBehavior(cancellationToken) is DeclarationBehavior.SplitIn or + DeclarationBehavior.MoveIn); + } - public IEnumerable GetVariablesToMoveIntoMethodDefinition(CancellationToken cancellationToken) - => Variables.Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveIn); + public IEnumerable GetVariablesToMoveIntoMethodDefinition(CancellationToken cancellationToken) + => Variables.Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveIn); - public IEnumerable GetVariablesToMoveOutToCallSite(CancellationToken cancellationToken) - => Variables.Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveOut); + public IEnumerable GetVariablesToMoveOutToCallSite(CancellationToken cancellationToken) + => Variables.Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveOut); - public IEnumerable GetVariablesToMoveOutToCallSiteOrDelete(CancellationToken cancellationToken) - { - return Variables.Where(v => v.GetDeclarationBehavior(cancellationToken) is DeclarationBehavior.MoveOut or - DeclarationBehavior.Delete); - } + public IEnumerable GetVariablesToMoveOutToCallSiteOrDelete(CancellationToken cancellationToken) + { + return Variables.Where(v => v.GetDeclarationBehavior(cancellationToken) is DeclarationBehavior.MoveOut or + DeclarationBehavior.Delete); + } - public IEnumerable GetVariablesToSplitOrMoveOutToCallSite(CancellationToken cancellationToken) - { - return Variables.Where(v => v.GetDeclarationBehavior(cancellationToken) is DeclarationBehavior.SplitOut or - DeclarationBehavior.MoveOut); - } + public IEnumerable GetVariablesToSplitOrMoveOutToCallSite(CancellationToken cancellationToken) + { + return Variables.Where(v => v.GetDeclarationBehavior(cancellationToken) is DeclarationBehavior.SplitOut or + DeclarationBehavior.MoveOut); + } - public VariableInfo GetOutermostVariableToMoveIntoMethodDefinition(CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var variables); - variables.AddRange(this.GetVariablesToMoveIntoMethodDefinition(cancellationToken)); - if (variables.Count <= 0) - return null; + public VariableInfo GetOutermostVariableToMoveIntoMethodDefinition(CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var variables); + variables.AddRange(this.GetVariablesToMoveIntoMethodDefinition(cancellationToken)); + if (variables.Count <= 0) + return null; - return variables.Min(); + return variables.Min(); + } } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs index cd21c3d159a41..0736522be67d7 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs @@ -21,372 +21,380 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - protected abstract class CodeGenerator + internal abstract partial class MethodExtractor { - /// - /// Used to produced the set of statements that will go into the generated method. - /// - public abstract OperationStatus> GetNewMethodStatements( - SyntaxNode insertionPointNode, CancellationToken cancellationToken); - } + protected abstract class CodeGenerator + { + /// + /// Used to produced the set of statements that will go into the generated method. + /// + public abstract OperationStatus> GetNewMethodStatements( + SyntaxNode insertionPointNode, CancellationToken cancellationToken); + } #pragma warning disable CS0693 // Intentionally hiding the outer TStatementSyntax - protected abstract partial class CodeGenerator : CodeGenerator + protected abstract partial class CodeGenerator : CodeGenerator #pragma warning restore CS0693 - where TStatementSyntax : SyntaxNode - where TNodeUnderContainer : SyntaxNode - where TCodeGenerationOptions : CodeGenerationOptions - { - private static readonly CodeGenerationContext s_codeGenerationContext = new(addImports: false); + where TStatementSyntax : SyntaxNode + where TNodeUnderContainer : SyntaxNode + where TCodeGenerationOptions : CodeGenerationOptions + { + private static readonly CodeGenerationContext s_codeGenerationContext = new(addImports: false); - protected readonly SyntaxAnnotation MethodNameAnnotation; - protected readonly SyntaxAnnotation MethodDefinitionAnnotation; - protected readonly SyntaxAnnotation CallSiteAnnotation; + protected readonly SyntaxAnnotation MethodNameAnnotation; + protected readonly SyntaxAnnotation MethodDefinitionAnnotation; + protected readonly SyntaxAnnotation CallSiteAnnotation; - protected readonly TSelectionResult SelectionResult; - protected readonly AnalyzerResult AnalyzerResult; + protected readonly TSelectionResult SelectionResult; + protected readonly AnalyzerResult AnalyzerResult; - protected readonly ExtractMethodGenerationOptions ExtractMethodGenerationOptions; - protected readonly TCodeGenerationOptions Options; + protected readonly ExtractMethodGenerationOptions ExtractMethodGenerationOptions; + protected readonly TCodeGenerationOptions Options; - protected readonly bool LocalFunction; + protected readonly bool LocalFunction; - protected CodeGenerator( - TSelectionResult selectionResult, - AnalyzerResult analyzerResult, - ExtractMethodGenerationOptions options, - bool localFunction) - { - SelectionResult = selectionResult; - AnalyzerResult = analyzerResult; + protected CodeGenerator( + TSelectionResult selectionResult, + AnalyzerResult analyzerResult, + ExtractMethodGenerationOptions options, + bool localFunction) + { + SelectionResult = selectionResult; + AnalyzerResult = analyzerResult; - ExtractMethodGenerationOptions = options; - Options = (TCodeGenerationOptions)options.CodeGenerationOptions; - LocalFunction = localFunction; + ExtractMethodGenerationOptions = options; + Options = (TCodeGenerationOptions)options.CodeGenerationOptions; + LocalFunction = localFunction; - MethodNameAnnotation = new SyntaxAnnotation(); - CallSiteAnnotation = new SyntaxAnnotation(); - MethodDefinitionAnnotation = new SyntaxAnnotation(); - } + MethodNameAnnotation = new SyntaxAnnotation(); + CallSiteAnnotation = new SyntaxAnnotation(); + MethodDefinitionAnnotation = new SyntaxAnnotation(); + } - protected SemanticDocument SemanticDocument => SelectionResult.SemanticDocument; + protected SemanticDocument SemanticDocument => SelectionResult.SemanticDocument; - #region method to be implemented in sub classes + #region method to be implemented in sub classes - protected abstract SyntaxNode GetCallSiteContainerFromOutermostMoveInVariable(CancellationToken cancellationToken); + protected abstract SyntaxNode GetCallSiteContainerFromOutermostMoveInVariable(CancellationToken cancellationToken); - protected abstract Task GenerateBodyForCallSiteContainerAsync(SyntaxNode insertionPointNode, SyntaxNode outermostCallSiteContainer, CancellationToken cancellationToken); - protected abstract IMethodSymbol GenerateMethodDefinition(SyntaxNode insertionPointNode, CancellationToken cancellationToken); - protected abstract bool ShouldLocalFunctionCaptureParameter(SyntaxNode node); + protected abstract Task GenerateBodyForCallSiteContainerAsync(SyntaxNode insertionPointNode, SyntaxNode outermostCallSiteContainer, CancellationToken cancellationToken); + protected abstract IMethodSymbol GenerateMethodDefinition(SyntaxNode insertionPointNode, CancellationToken cancellationToken); + protected abstract bool ShouldLocalFunctionCaptureParameter(SyntaxNode node); - protected abstract SyntaxToken CreateIdentifier(string name); - protected abstract SyntaxToken CreateMethodName(); - protected abstract bool LastStatementOrHasReturnStatementInReturnableConstruct(); + protected abstract SyntaxToken CreateIdentifier(string name); + protected abstract SyntaxToken CreateMethodName(); + protected abstract bool LastStatementOrHasReturnStatementInReturnableConstruct(); - protected abstract TNodeUnderContainer GetFirstStatementOrInitializerSelectedAtCallSite(); - protected abstract TNodeUnderContainer GetLastStatementOrInitializerSelectedAtCallSite(); - protected abstract Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken); + protected abstract TNodeUnderContainer GetFirstStatementOrInitializerSelectedAtCallSite(); + protected abstract TNodeUnderContainer GetLastStatementOrInitializerSelectedAtCallSite(); + protected abstract Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken); - protected abstract TExpressionSyntax CreateCallSignature(); - protected abstract TStatementSyntax CreateDeclarationStatement(ImmutableArray variables, TExpressionSyntax initialValue, CancellationToken cancellationToken); - protected abstract TStatementSyntax CreateAssignmentExpressionStatement(ImmutableArray variables, TExpressionSyntax rvalue); - protected abstract TStatementSyntax CreateReturnStatement(params string[] identifierNames); + protected abstract TExpressionSyntax CreateCallSignature(); + protected abstract TStatementSyntax CreateDeclarationStatement(ImmutableArray variables, TExpressionSyntax initialValue, CancellationToken cancellationToken); + protected abstract TStatementSyntax CreateAssignmentExpressionStatement(ImmutableArray variables, TExpressionSyntax rvalue); + protected abstract TStatementSyntax CreateReturnStatement(params string[] identifierNames); - protected abstract ImmutableArray GetInitialStatementsForMethodDefinitions(); + protected abstract ImmutableArray GetInitialStatementsForMethodDefinitions(); - protected abstract Task UpdateMethodAfterGenerationAsync( - SemanticDocument originalDocument, IMethodSymbol methodSymbolResult, CancellationToken cancellationToken); + protected abstract Task UpdateMethodAfterGenerationAsync( + SemanticDocument originalDocument, IMethodSymbol methodSymbolResult, CancellationToken cancellationToken); - #endregion + #endregion - public async Task GenerateAsync(InsertionPoint insertionPoint, CancellationToken cancellationToken) - { - var newMethodDefinition = GenerateMethodDefinition(insertionPoint.GetContext(), cancellationToken); - var callSiteDocument = await InsertMethodAndUpdateCallSiteAsync(insertionPoint, newMethodDefinition, cancellationToken).ConfigureAwait(false); - - // For nullable reference types, we can provide a better experience by reducing use of nullable - // reference types after a method is done being generated. If we can determine that the method never - // returns null, for example, then we can make the signature into a non-null reference type even though - // the original type was nullable. This allows our code generation to follow our recommendation of only - // using nullable when necessary. This is done after method generation instead of at analyzer time - // because it's purely based on the resulting code, which the generator can modify as needed. If return - // statements are added, the flow analysis could change to indicate something different. It's cleaner to - // rely on flow analysis of the final resulting code than to try and predict from the analyzer what will - // happen in the generator. - var finalDocument = await UpdateMethodAfterGenerationAsync(callSiteDocument, newMethodDefinition, cancellationToken).ConfigureAwait(false); - - return await CreateGeneratedCodeAsync(finalDocument, cancellationToken).ConfigureAwait(false); - } + public async Task GenerateAsync(InsertionPoint insertionPoint, CancellationToken cancellationToken) + { + var newMethodDefinition = GenerateMethodDefinition(insertionPoint.GetContext(), cancellationToken); + var callSiteDocument = await InsertMethodAndUpdateCallSiteAsync(insertionPoint, newMethodDefinition, cancellationToken).ConfigureAwait(false); + + // For nullable reference types, we can provide a better experience by reducing use of nullable + // reference types after a method is done being generated. If we can determine that the method never + // returns null, for example, then we can make the signature into a non-null reference type even though + // the original type was nullable. This allows our code generation to follow our recommendation of only + // using nullable when necessary. This is done after method generation instead of at analyzer time + // because it's purely based on the resulting code, which the generator can modify as needed. If return + // statements are added, the flow analysis could change to indicate something different. It's cleaner to + // rely on flow analysis of the final resulting code than to try and predict from the analyzer what will + // happen in the generator. + var finalDocument = await UpdateMethodAfterGenerationAsync(callSiteDocument, newMethodDefinition, cancellationToken).ConfigureAwait(false); + + return await CreateGeneratedCodeAsync(finalDocument, cancellationToken).ConfigureAwait(false); + } - private async Task InsertMethodAndUpdateCallSiteAsync( - InsertionPoint insertionPoint, IMethodSymbol newMethodDefinition, CancellationToken cancellationToken) - { - var document = this.SemanticDocument.Document; - var codeGenerationService = document.GetLanguageService(); + private async Task InsertMethodAndUpdateCallSiteAsync( + InsertionPoint insertionPoint, IMethodSymbol newMethodDefinition, CancellationToken cancellationToken) + { + var document = this.SemanticDocument.Document; + var codeGenerationService = document.GetLanguageService(); - // First, update the callsite with the call to the new method. - var outermostCallSiteContainer = GetOutermostCallSiteContainerToProcess(cancellationToken); + // First, update the callsite with the call to the new method. + var outermostCallSiteContainer = GetOutermostCallSiteContainerToProcess(cancellationToken); - var rootWithUpdatedCallSite = this.SemanticDocument.Root.ReplaceNode( - outermostCallSiteContainer, - await GenerateBodyForCallSiteContainerAsync( - insertionPoint.GetContext(), outermostCallSiteContainer, cancellationToken).ConfigureAwait(false)); + var rootWithUpdatedCallSite = this.SemanticDocument.Root.ReplaceNode( + outermostCallSiteContainer, + await GenerateBodyForCallSiteContainerAsync( + insertionPoint.GetContext(), outermostCallSiteContainer, cancellationToken).ConfigureAwait(false)); - // Then insert the local-function/method into the updated document that contains the updated callsite. - var documentWithUpdatedCallSite = await this.SemanticDocument.WithSyntaxRootAsync(rootWithUpdatedCallSite, cancellationToken).ConfigureAwait(false); - var finalRoot = LocalFunction - ? InsertLocalFunction() - : InsertNormalMethod(); + // Then insert the local-function/method into the updated document that contains the updated callsite. + var documentWithUpdatedCallSite = await this.SemanticDocument.WithSyntaxRootAsync(rootWithUpdatedCallSite, cancellationToken).ConfigureAwait(false); + var finalRoot = LocalFunction + ? InsertLocalFunction() + : InsertNormalMethod(); - return await documentWithUpdatedCallSite.WithSyntaxRootAsync(finalRoot, cancellationToken).ConfigureAwait(false); + return await documentWithUpdatedCallSite.WithSyntaxRootAsync(finalRoot, cancellationToken).ConfigureAwait(false); - SyntaxNode InsertLocalFunction() - { - // Now, insert the local function. - var info = codeGenerationService.GetInfo( - s_codeGenerationContext.With(generateDefaultAccessibility: false), - Options, - document.Project.ParseOptions); + SyntaxNode InsertLocalFunction() + { + // Now, insert the local function. + var info = codeGenerationService.GetInfo( + s_codeGenerationContext.With(generateDefaultAccessibility: false), + Options, + document.Project.ParseOptions); - var localMethod = codeGenerationService.CreateMethodDeclaration(newMethodDefinition, CodeGenerationDestination.Unspecified, info, cancellationToken); + var localMethod = codeGenerationService.CreateMethodDeclaration(newMethodDefinition, CodeGenerationDestination.Unspecified, info, cancellationToken); - // Find the destination for the local function after the callsite has been fixed up. - var destination = insertionPoint.With(documentWithUpdatedCallSite).GetContext(); - var updatedDestination = codeGenerationService.AddStatements(destination, [localMethod], info, cancellationToken); + // Find the destination for the local function after the callsite has been fixed up. + var destination = insertionPoint.With(documentWithUpdatedCallSite).GetContext(); + var updatedDestination = codeGenerationService.AddStatements(destination, [localMethod], info, cancellationToken); - var finalRoot = documentWithUpdatedCallSite.Root.ReplaceNode(destination, updatedDestination); - return finalRoot; - } + var finalRoot = documentWithUpdatedCallSite.Root.ReplaceNode(destination, updatedDestination); + return finalRoot; + } - SyntaxNode InsertNormalMethod() - { - var syntaxKinds = document.GetLanguageService(); - - // Find the destination for the new method after the callsite has been fixed up. - var mappedMember = insertionPoint.With(documentWithUpdatedCallSite).GetContext(); - mappedMember = mappedMember.Parent?.RawKind == syntaxKinds.GlobalStatement - ? mappedMember.Parent - : mappedMember; - - mappedMember = mappedMember.RawKind == syntaxKinds.PrimaryConstructorBaseType - ? mappedMember.Parent - : mappedMember; - - // it is possible in a script file case where there is no previous member. in that case, insert new text into top level script - var destination = mappedMember.Parent ?? mappedMember; - - var info = codeGenerationService.GetInfo( - s_codeGenerationContext.With( - afterThisLocation: mappedMember.GetLocation(), - generateDefaultAccessibility: true, - generateMethodBodies: true), - Options, - documentWithUpdatedCallSite.Project.ParseOptions); - - var newContainer = codeGenerationService.AddMethod(destination, newMethodDefinition, info, cancellationToken); - var finalRoot = documentWithUpdatedCallSite.Root.ReplaceNode(destination, newContainer); - return finalRoot; + SyntaxNode InsertNormalMethod() + { + var syntaxKinds = document.GetLanguageService(); + + // Find the destination for the new method after the callsite has been fixed up. + var mappedMember = insertionPoint.With(documentWithUpdatedCallSite).GetContext(); + mappedMember = mappedMember.Parent?.RawKind == syntaxKinds.GlobalStatement + ? mappedMember.Parent + : mappedMember; + + mappedMember = mappedMember.RawKind == syntaxKinds.PrimaryConstructorBaseType + ? mappedMember.Parent + : mappedMember; + + // it is possible in a script file case where there is no previous member. in that case, insert new text into top level script + var destination = mappedMember.Parent ?? mappedMember; + + var info = codeGenerationService.GetInfo( + s_codeGenerationContext.With( + afterThisLocation: mappedMember.GetLocation(), + generateDefaultAccessibility: true, + generateMethodBodies: true), + Options, + documentWithUpdatedCallSite.Project.ParseOptions); + + var newContainer = codeGenerationService.AddMethod(destination, newMethodDefinition, info, cancellationToken); + var finalRoot = documentWithUpdatedCallSite.Root.ReplaceNode(destination, newContainer); + return finalRoot; + } } - } - private SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken) - { - var callSiteContainer = GetCallSiteContainerFromOutermostMoveInVariable(cancellationToken); - if (callSiteContainer != null) - { - return callSiteContainer; - } - else + private SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken) { - return this.SelectionResult.GetOutermostCallSiteContainerToProcess(cancellationToken); + var callSiteContainer = GetCallSiteContainerFromOutermostMoveInVariable(cancellationToken); + if (callSiteContainer != null) + { + return callSiteContainer; + } + else + { + return this.SelectionResult.GetOutermostCallSiteContainerToProcess(cancellationToken); + } } - } - - protected virtual Task CreateGeneratedCodeAsync(SemanticDocument newDocument, CancellationToken cancellationToken) - { - return Task.FromResult(new GeneratedCode( - newDocument, - MethodNameAnnotation, - CallSiteAnnotation, - MethodDefinitionAnnotation)); - } - protected VariableInfo GetOutermostVariableToMoveIntoMethodDefinition(CancellationToken cancellationToken) - { - return this.AnalyzerResult.GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken); - } - - protected ImmutableArray AddReturnIfUnreachable(ImmutableArray statements) - { - if (AnalyzerResult.EndOfSelectionReachable) + protected virtual Task CreateGeneratedCodeAsync(SemanticDocument newDocument, CancellationToken cancellationToken) { - return statements; + return Task.FromResult(new GeneratedCode( + newDocument, + MethodNameAnnotation, + CallSiteAnnotation, + MethodDefinitionAnnotation)); } - var type = SelectionResult.GetContainingScopeType(); - if (type != null && type.SpecialType != SpecialType.System_Void) + protected VariableInfo GetOutermostVariableToMoveIntoMethodDefinition(CancellationToken cancellationToken) { - return statements; + return this.AnalyzerResult.GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken); } - // no return type + end of selection not reachable - if (LastStatementOrHasReturnStatementInReturnableConstruct()) + protected ImmutableArray AddReturnIfUnreachable(ImmutableArray statements) { - return statements; - } - - return statements.Concat(CreateReturnStatement()); - } - - protected async Task> AddInvocationAtCallSiteAsync( - ImmutableArray statements, CancellationToken cancellationToken) - { - if (!AnalyzerResult.VariablesToUseAsReturnValue.IsEmpty) - return statements; + if (AnalyzerResult.EndOfSelectionReachable) + { + return statements; + } - Contract.ThrowIfTrue(AnalyzerResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken).Any(v => v.UseAsReturnValue)); + var type = SelectionResult.GetContainingScopeType(); + if (type != null && type.SpecialType != SpecialType.System_Void) + { + return statements; + } - // add invocation expression - return statements.Concat( - (TStatementSyntax)(SyntaxNode)await GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken).ConfigureAwait(false)); - } + // no return type + end of selection not reachable + if (LastStatementOrHasReturnStatementInReturnableConstruct()) + { + return statements; + } - protected ImmutableArray AddAssignmentStatementToCallSite( - ImmutableArray statements, - CancellationToken cancellationToken) - { - if (AnalyzerResult.VariablesToUseAsReturnValue.IsEmpty) - return statements; + return statements.Concat(CreateReturnStatement()); + } - var variables = AnalyzerResult.VariablesToUseAsReturnValue; - if (variables.Any(v => v.ReturnBehavior == ReturnBehavior.Initialization)) + protected async Task> AddInvocationAtCallSiteAsync( + ImmutableArray statements, CancellationToken cancellationToken) { - var declarationStatement = CreateDeclarationStatement( - variables, CreateCallSignature(), cancellationToken); - declarationStatement = declarationStatement.WithAdditionalAnnotations(CallSiteAnnotation); + if (!AnalyzerResult.VariablesToUseAsReturnValue.IsEmpty) + return statements; + + Contract.ThrowIfTrue(AnalyzerResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken).Any(v => v.UseAsReturnValue)); - return statements.Concat(declarationStatement); + // add invocation expression + return statements.Concat( + (TStatementSyntax)(SyntaxNode)await GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken).ConfigureAwait(false)); } - return statements.Concat( - CreateAssignmentExpressionStatement(variables, CreateCallSignature()).WithAdditionalAnnotations(CallSiteAnnotation)); - } + protected ImmutableArray AddAssignmentStatementToCallSite( + ImmutableArray statements, + CancellationToken cancellationToken) + { + if (AnalyzerResult.VariablesToUseAsReturnValue.IsEmpty) + return statements; - protected ImmutableArray CreateDeclarationStatements( - ImmutableArray variables, CancellationToken cancellationToken) - { - return variables.SelectAsArray(v => CreateDeclarationStatement([v], initialValue: null, cancellationToken)); - } + var variables = AnalyzerResult.VariablesToUseAsReturnValue; + if (variables.Any(v => v.ReturnBehavior == ReturnBehavior.Initialization)) + { + var declarationStatement = CreateDeclarationStatement( + variables, CreateCallSignature(), cancellationToken); + declarationStatement = declarationStatement.WithAdditionalAnnotations(CallSiteAnnotation); - protected ImmutableArray AddSplitOrMoveDeclarationOutStatementsToCallSite( - CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var list); + return statements.Concat(declarationStatement); + } - foreach (var variable in AnalyzerResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken)) - { - if (variable.UseAsReturnValue) - continue; + return statements.Concat( + CreateAssignmentExpressionStatement(variables, CreateCallSignature()).WithAdditionalAnnotations(CallSiteAnnotation)); + } - list.Add(CreateDeclarationStatement( - [variable], initialValue: null, cancellationToken: cancellationToken)); + protected ImmutableArray CreateDeclarationStatements( + ImmutableArray variables, CancellationToken cancellationToken) + { + return variables.SelectAsArray(v => CreateDeclarationStatement([v], initialValue: null, cancellationToken)); } - return list.ToImmutableAndClear(); - } + protected ImmutableArray AddSplitOrMoveDeclarationOutStatementsToCallSite( + CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var list); - protected ImmutableArray AppendReturnStatementIfNeeded(ImmutableArray statements) - { - if (AnalyzerResult.VariablesToUseAsReturnValue.IsEmpty) - return statements; + foreach (var variable in AnalyzerResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken)) + { + if (variable.UseAsReturnValue) + continue; - return statements.Concat(CreateReturnStatement([.. AnalyzerResult.VariablesToUseAsReturnValue.Select(b => b.Name)])); - } + list.Add(CreateDeclarationStatement( + [variable], initialValue: null, cancellationToken: cancellationToken)); + } - protected static HashSet CreateVariableDeclarationToRemoveMap( - IEnumerable variables, CancellationToken cancellationToken) - { - var annotations = new List<(SyntaxToken, SyntaxAnnotation)>(); + return list.ToImmutableAndClear(); + } - foreach (var variable in variables) + protected ImmutableArray AppendReturnStatementIfNeeded(ImmutableArray statements) { - Contract.ThrowIfFalse(variable.GetDeclarationBehavior(cancellationToken) is DeclarationBehavior.MoveOut or - DeclarationBehavior.MoveIn or - DeclarationBehavior.Delete); + if (AnalyzerResult.VariablesToUseAsReturnValue.IsEmpty) + return statements; - variable.AddIdentifierTokenAnnotationPair(annotations, cancellationToken); + return statements.Concat(CreateReturnStatement([.. AnalyzerResult.VariablesToUseAsReturnValue.Select(b => b.Name)])); } - return [.. annotations.Select(t => t.Item2)]; - } + protected static HashSet CreateVariableDeclarationToRemoveMap( + IEnumerable variables, CancellationToken cancellationToken) + { + var annotations = new List<(SyntaxToken, SyntaxAnnotation)>(); - protected ImmutableArray CreateMethodTypeParameters() - { - if (AnalyzerResult.MethodTypeParametersInDeclaration.IsEmpty) - return []; + foreach (var variable in variables) + { + Contract.ThrowIfFalse(variable.GetDeclarationBehavior(cancellationToken) is DeclarationBehavior.MoveOut or + DeclarationBehavior.MoveIn or + DeclarationBehavior.Delete); - var set = new HashSet(AnalyzerResult.MethodTypeParametersInConstraintList); + variable.AddIdentifierTokenAnnotationPair(annotations, cancellationToken); + } + + return new HashSet(annotations.Select(t => t.Item2)); + } - var typeParameters = ArrayBuilder.GetInstance(); - foreach (var parameter in AnalyzerResult.MethodTypeParametersInDeclaration) + protected ImmutableArray CreateMethodTypeParameters() { - if (parameter != null && set.Contains(parameter)) + if (AnalyzerResult.MethodTypeParametersInDeclaration.IsEmpty) + return []; + + var set = new HashSet(AnalyzerResult.MethodTypeParametersInConstraintList); + + var typeParameters = ArrayBuilder.GetInstance(); + foreach (var parameter in AnalyzerResult.MethodTypeParametersInDeclaration) { - typeParameters.Add(parameter); - continue; + if (parameter != null && set.Contains(parameter)) + { + typeParameters.Add(parameter); + continue; + } + + typeParameters.Add(CodeGenerationSymbolFactory.CreateTypeParameter( + parameter.GetAttributes(), parameter.Variance, parameter.Name, [], parameter.NullableAnnotation, + parameter.HasConstructorConstraint, parameter.HasReferenceTypeConstraint, parameter.HasUnmanagedTypeConstraint, + parameter.HasValueTypeConstraint, parameter.HasNotNullConstraint, parameter.AllowsRefLikeType, parameter.Ordinal)); } - typeParameters.Add(CodeGenerationSymbolFactory.CreateTypeParameter( - parameter.GetAttributes(), parameter.Variance, parameter.Name, [], parameter.NullableAnnotation, - parameter.HasConstructorConstraint, parameter.HasReferenceTypeConstraint, parameter.HasUnmanagedTypeConstraint, - parameter.HasValueTypeConstraint, parameter.HasNotNullConstraint, parameter.AllowsRefLikeType, parameter.Ordinal)); + return typeParameters.ToImmutableAndFree(); } - return typeParameters.ToImmutableAndFree(); - } - - protected ImmutableArray CreateMethodParameters() - { - using var _ = ArrayBuilder.GetInstance(out var parameters); - var isLocalFunction = LocalFunction && ShouldLocalFunctionCaptureParameter(SemanticDocument.Root); - foreach (var parameter in AnalyzerResult.MethodParameters) + protected ImmutableArray CreateMethodParameters() { - if (!isLocalFunction || !parameter.CanBeCapturedByLocalFunction) + using var _ = ArrayBuilder.GetInstance(out var parameters); + var isLocalFunction = LocalFunction && ShouldLocalFunctionCaptureParameter(SemanticDocument.Root); + foreach (var parameter in AnalyzerResult.MethodParameters) { - var refKind = GetRefKind(parameter.ParameterModifier); - var type = parameter.GetVariableType(); + if (!isLocalFunction || !parameter.CanBeCapturedByLocalFunction) + { + var refKind = GetRefKind(parameter.ParameterModifier); + var type = parameter.GetVariableType(); - parameters.Add( - CodeGenerationSymbolFactory.CreateParameterSymbol( + parameters.Add(CodeGenerationSymbolFactory.CreateParameterSymbol( attributes: [], refKind: refKind, isParams: false, type: type, name: parameter.Name)); + } } + + return parameters.ToImmutableAndClear(); } - return parameters.ToImmutableAndClear(); - } + private static RefKind GetRefKind(ParameterBehavior parameterBehavior) + => parameterBehavior switch + { + ParameterBehavior.Ref => RefKind.Ref, + ParameterBehavior.Out => RefKind.Out, + _ => RefKind.None + }; - private static RefKind GetRefKind(ParameterBehavior parameterBehavior) - => parameterBehavior switch + protected TStatementSyntax GetStatementContainingInvocationToExtractedMethodWorker() { - ParameterBehavior.Ref => RefKind.Ref, - ParameterBehavior.Out => RefKind.Out, - _ => RefKind.None - }; + var callSignature = CreateCallSignature(); - protected TStatementSyntax GetStatementContainingInvocationToExtractedMethodWorker() - { - var callSignature = CreateCallSignature(); - - var generator = this.SemanticDocument.Document.GetRequiredLanguageService(); - return AnalyzerResult.HasReturnType - ? (TStatementSyntax)generator.ReturnStatement(callSignature) - : (TStatementSyntax)generator.ExpressionStatement(callSignature); + var generator = this.SemanticDocument.Document.GetRequiredLanguageService(); + return AnalyzerResult.HasReturnType + ? (TStatementSyntax)generator.ReturnStatement(callSignature) + : (TStatementSyntax)generator.ExpressionStatement(callSignature); + } } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.GeneratedCode.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.GeneratedCode.cs index 7e7c440aaea7a..fd4362f0ff138 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.GeneratedCode.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.GeneratedCode.cs @@ -6,31 +6,40 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - internal sealed class GeneratedCode + internal abstract partial class MethodExtractor { - public GeneratedCode( - SemanticDocument document, - SyntaxAnnotation methodNameAnnotation, - SyntaxAnnotation callSiteAnnotation, - SyntaxAnnotation methodDefinitionAnnotation) + internal sealed class GeneratedCode { - Contract.ThrowIfNull(document); - Contract.ThrowIfNull(methodNameAnnotation); - Contract.ThrowIfNull(callSiteAnnotation); - Contract.ThrowIfNull(methodDefinitionAnnotation); + public GeneratedCode( + SemanticDocument document, + SyntaxAnnotation methodNameAnnotation, + SyntaxAnnotation callSiteAnnotation, + SyntaxAnnotation methodDefinitionAnnotation) + { + Contract.ThrowIfNull(document); + Contract.ThrowIfNull(methodNameAnnotation); + Contract.ThrowIfNull(callSiteAnnotation); + Contract.ThrowIfNull(methodDefinitionAnnotation); - SemanticDocument = document; - MethodNameAnnotation = methodNameAnnotation; - CallSiteAnnotation = callSiteAnnotation; - MethodDefinitionAnnotation = methodDefinitionAnnotation; - } + SemanticDocument = document; + MethodNameAnnotation = methodNameAnnotation; + CallSiteAnnotation = callSiteAnnotation; + MethodDefinitionAnnotation = methodDefinitionAnnotation; + } - public SemanticDocument SemanticDocument { get; } + public SemanticDocument SemanticDocument { get; } - public SyntaxAnnotation MethodNameAnnotation { get; } - public SyntaxAnnotation CallSiteAnnotation { get; } - public SyntaxAnnotation MethodDefinitionAnnotation { get; } + public SyntaxAnnotation MethodNameAnnotation { get; } + public SyntaxAnnotation CallSiteAnnotation { get; } + public SyntaxAnnotation MethodDefinitionAnnotation { get; } + } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.TriviaResult.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.TriviaResult.cs index 29d4abaf46f8a..806042fe4cb71 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.TriviaResult.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.TriviaResult.cs @@ -13,159 +13,168 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - protected abstract class TriviaResult(SemanticDocument document, ITriviaSavedResult result, int endOfLineKind, int whitespaceKind) + internal abstract partial class MethodExtractor { - private readonly int _endOfLineKind = endOfLineKind; - private readonly int _whitespaceKind = whitespaceKind; - - private readonly ITriviaSavedResult _result = result; + protected abstract class TriviaResult(SemanticDocument document, ITriviaSavedResult result, int endOfLineKind, int whitespaceKind) + { + private readonly int _endOfLineKind = endOfLineKind; + private readonly int _whitespaceKind = whitespaceKind; - protected abstract AnnotationResolver GetAnnotationResolver(SyntaxNode callsite, SyntaxNode methodDefinition); - protected abstract TriviaResolver GetTriviaResolver(SyntaxNode methodDefinition); + private readonly ITriviaSavedResult _result = result; - public SemanticDocument SemanticDocument { get; } = document; + protected abstract AnnotationResolver GetAnnotationResolver(SyntaxNode callsite, SyntaxNode methodDefinition); + protected abstract TriviaResolver GetTriviaResolver(SyntaxNode methodDefinition); - public async Task ApplyAsync(GeneratedCode generatedCode, CancellationToken cancellationToken) - { - var document = generatedCode.SemanticDocument; - var root = document.Root; + public SemanticDocument SemanticDocument { get; } = document; - var callsiteAnnotation = generatedCode.CallSiteAnnotation; - var methodDefinitionAnnotation = generatedCode.MethodDefinitionAnnotation; + public async Task ApplyAsync(GeneratedCode generatedCode, CancellationToken cancellationToken) + { + var document = generatedCode.SemanticDocument; + var root = document.Root; - var callsite = root.GetAnnotatedNodesAndTokens(callsiteAnnotation).SingleOrDefault().AsNode(); - var method = root.GetAnnotatedNodesAndTokens(methodDefinitionAnnotation).SingleOrDefault().AsNode(); + var callsiteAnnotation = generatedCode.CallSiteAnnotation; + var methodDefinitionAnnotation = generatedCode.MethodDefinitionAnnotation; - var annotationResolver = GetAnnotationResolver(callsite, method); - var triviaResolver = GetTriviaResolver(method); + var callsite = root.GetAnnotatedNodesAndTokens(callsiteAnnotation).SingleOrDefault().AsNode(); + var method = root.GetAnnotatedNodesAndTokens(methodDefinitionAnnotation).SingleOrDefault().AsNode(); - // Failed to restore the trivia. Just return whatever best effort result is that we have so far. - if (annotationResolver == null || triviaResolver == null) - return document; + var annotationResolver = GetAnnotationResolver(callsite, method); + var triviaResolver = GetTriviaResolver(method); - return await document.WithSyntaxRootAsync( - _result.RestoreTrivia(root, annotationResolver, triviaResolver), cancellationToken).ConfigureAwait(false); - } + // Failed to restore the trivia. Just return whatever best effort result is that we have so far. + if (annotationResolver == null || triviaResolver == null) + return document; - protected IEnumerable FilterTriviaList(IEnumerable list) - { - // has noisy token - if (list.Any(t => t.RawKind != _endOfLineKind && t.RawKind != _whitespaceKind)) - { - return RemoveLeadingElasticBeforeEndOfLine(list); + return await document.WithSyntaxRootAsync( + _result.RestoreTrivia(root, annotationResolver, triviaResolver), cancellationToken).ConfigureAwait(false); } - // whitespace only - return MergeLineBreaks(list); - } - - protected IEnumerable RemoveBlankLines(IEnumerable list) - { - // remove any blank line at the beginning - var currentLine = new List(); - var result = new List(); + protected IEnumerable FilterTriviaList(IEnumerable list) + { + // has noisy token + if (list.Any(t => t.RawKind != _endOfLineKind && t.RawKind != _whitespaceKind)) + { + return RemoveLeadingElasticBeforeEndOfLine(list); + } - var seenFirstEndOfLine = false; - var i = 0; + // whitespace only + return MergeLineBreaks(list); + } - foreach (var trivia in list) + protected IEnumerable RemoveBlankLines(IEnumerable list) { - i++; + // remove any blank line at the beginning + var currentLine = new List(); + var result = new List(); - if (trivia.RawKind == _endOfLineKind) + var seenFirstEndOfLine = false; + var i = 0; + + foreach (var trivia in list) { - if (seenFirstEndOfLine) + i++; + + if (trivia.RawKind == _endOfLineKind) { - // empty line. remove it - if (currentLine.All(t => t.RawKind == _endOfLineKind || t.RawKind == _whitespaceKind)) + if (seenFirstEndOfLine) { - continue; + // empty line. remove it + if (currentLine.All(t => t.RawKind == _endOfLineKind || t.RawKind == _whitespaceKind)) + { + continue; + } + + // non empty line after the first end of line. + // return now + return result.Concat(currentLine).Concat(list.Skip(i - 1)); } + else + { + seenFirstEndOfLine = true; - // non empty line after the first end of line. - // return now - return result.Concat(currentLine).Concat(list.Skip(i - 1)); - } - else - { - seenFirstEndOfLine = true; - - result.AddRange(currentLine); - result.Add(trivia); - currentLine.Clear(); + result.AddRange(currentLine); + result.Add(trivia); + currentLine.Clear(); - continue; + continue; + } } - } - - currentLine.Add(trivia); - } - return result.Concat(currentLine); - } - - protected IEnumerable RemoveLeadingElasticBeforeEndOfLine(IEnumerable list) - { - var trivia = list.FirstOrDefault(); - if (!trivia.IsElastic()) - { - return list; - } + currentLine.Add(trivia); + } - var listWithoutHead = list.Skip(1); - trivia = listWithoutHead.FirstOrDefault(); - if (trivia.RawKind == _endOfLineKind) - { - return listWithoutHead; + return result.Concat(currentLine); } - if (trivia.IsElastic()) + protected IEnumerable RemoveLeadingElasticBeforeEndOfLine(IEnumerable list) { - return RemoveLeadingElasticBeforeEndOfLine(listWithoutHead); - } - - return list; - } + var trivia = list.FirstOrDefault(); + if (!trivia.IsElastic()) + { + return list; + } - protected IEnumerable MergeLineBreaks(IEnumerable list) - { - // this will make sure that it doesn't have more than two subsequent end of line - // trivia without any noisy trivia - var stack = new Stack(); - var numberOfEndOfLinesWithoutAnyNoisyTrivia = 0; + var listWithoutHead = list.Skip(1); + trivia = listWithoutHead.FirstOrDefault(); + if (trivia.RawKind == _endOfLineKind) + { + return listWithoutHead; + } - foreach (var trivia in list) - { if (trivia.IsElastic()) { - stack.Push(trivia); - continue; + return RemoveLeadingElasticBeforeEndOfLine(listWithoutHead); } - if (trivia.RawKind == _endOfLineKind) + return list; + } + + protected IEnumerable MergeLineBreaks(IEnumerable list) + { + // this will make sure that it doesn't have more than two subsequent end of line + // trivia without any noisy trivia + var stack = new Stack(); + var numberOfEndOfLinesWithoutAnyNoisyTrivia = 0; + + foreach (var trivia in list) { - numberOfEndOfLinesWithoutAnyNoisyTrivia++; + if (trivia.IsElastic()) + { + stack.Push(trivia); + continue; + } - if (numberOfEndOfLinesWithoutAnyNoisyTrivia > 2) + if (trivia.RawKind == _endOfLineKind) { - // get rid of any whitespace trivia from stack - var top = stack.Peek(); - while (!top.IsElastic() && top.RawKind == _whitespaceKind) + numberOfEndOfLinesWithoutAnyNoisyTrivia++; + + if (numberOfEndOfLinesWithoutAnyNoisyTrivia > 2) { - stack.Pop(); - top = stack.Peek(); - } + // get rid of any whitespace trivia from stack + var top = stack.Peek(); + while (!top.IsElastic() && top.RawKind == _whitespaceKind) + { + stack.Pop(); + top = stack.Peek(); + } - continue; + continue; + } } + + stack.Push(trivia); } - stack.Push(trivia); + return stack.Reverse(); } - - return stack.Reverse(); } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.TypeParameterCollector.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.TypeParameterCollector.cs index e8bab05fcf286..f5230751886e5 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.TypeParameterCollector.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.TypeParameterCollector.cs @@ -10,51 +10,60 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - protected class TypeParameterCollector : SymbolVisitor + internal abstract partial class MethodExtractor { - private readonly List _typeParameters = []; - - public static IEnumerable Collect(ITypeSymbol typeSymbol) + protected class TypeParameterCollector : SymbolVisitor { - var collector = new TypeParameterCollector(); - typeSymbol.Accept(collector); + private readonly List _typeParameters = []; - return collector._typeParameters; - } + public static IEnumerable Collect(ITypeSymbol typeSymbol) + { + var collector = new TypeParameterCollector(); + typeSymbol.Accept(collector); - public override void DefaultVisit(ISymbol node) - => throw new NotImplementedException(); + return collector._typeParameters; + } - public override void VisitDynamicType(IDynamicTypeSymbol dynamicTypeSymbol) - { - } + public override void DefaultVisit(ISymbol node) + => throw new NotImplementedException(); - public override void VisitFunctionPointerType(IFunctionPointerTypeSymbol symbol) - { - symbol.Signature.ReturnType.Accept(this); - foreach (var param in symbol.Signature.Parameters) + public override void VisitDynamicType(IDynamicTypeSymbol dynamicTypeSymbol) { - param.Type.Accept(this); } - } - public override void VisitArrayType(IArrayTypeSymbol arrayTypeSymbol) - => arrayTypeSymbol.ElementType.Accept(this); + public override void VisitFunctionPointerType(IFunctionPointerTypeSymbol symbol) + { + symbol.Signature.ReturnType.Accept(this); + foreach (var param in symbol.Signature.Parameters) + { + param.Type.Accept(this); + } + } - public override void VisitPointerType(IPointerTypeSymbol pointerTypeSymbol) - => pointerTypeSymbol.PointedAtType.Accept(this); + public override void VisitArrayType(IArrayTypeSymbol arrayTypeSymbol) + => arrayTypeSymbol.ElementType.Accept(this); - public override void VisitNamedType(INamedTypeSymbol namedTypeSymbol) - { - foreach (var argument in namedTypeSymbol.GetAllTypeArguments()) + public override void VisitPointerType(IPointerTypeSymbol pointerTypeSymbol) + => pointerTypeSymbol.PointedAtType.Accept(this); + + public override void VisitNamedType(INamedTypeSymbol namedTypeSymbol) { - argument.Accept(this); + foreach (var argument in namedTypeSymbol.GetAllTypeArguments()) + { + argument.Accept(this); + } } - } - public override void VisitTypeParameter(ITypeParameterSymbol typeParameterTypeSymbol) - => _typeParameters.Add(typeParameterTypeSymbol); + public override void VisitTypeParameter(ITypeParameterSymbol typeParameterTypeSymbol) + => _typeParameters.Add(typeParameterTypeSymbol); + } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs index 68153c75b4f6c..38715fd6469f9 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs @@ -12,123 +12,131 @@ using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExtractMethod; - -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - protected class VariableInfo( - VariableSymbol variableSymbol, - VariableStyle variableStyle, - bool useAsReturnValue = false) : IComparable + internal abstract partial class MethodExtractor { - private readonly VariableSymbol _variableSymbol = variableSymbol; - private readonly VariableStyle _variableStyle = variableStyle; - private readonly bool _useAsReturnValue = useAsReturnValue; - - public bool UseAsReturnValue + protected sealed class VariableInfo( + VariableSymbol variableSymbol, + VariableStyle variableStyle, + bool useAsReturnValue) : IComparable { - get - { - Contract.ThrowIfFalse(!_useAsReturnValue || _variableStyle.ReturnStyle.ReturnBehavior != ReturnBehavior.None); - return _useAsReturnValue; - } - } + private readonly VariableSymbol _variableSymbol = variableSymbol; + private readonly VariableStyle _variableStyle = variableStyle; + private readonly bool _useAsReturnValue = useAsReturnValue; - public bool CanBeUsedAsReturnValue - { - get + public bool UseAsReturnValue { - return _variableStyle.ReturnStyle.ReturnBehavior != ReturnBehavior.None; + get + { + Contract.ThrowIfFalse(!_useAsReturnValue || _variableStyle.ReturnStyle.ReturnBehavior != ReturnBehavior.None); + return _useAsReturnValue; + } } - } - public bool UseAsParameter - { - get + public bool CanBeUsedAsReturnValue { - return (!_useAsReturnValue && _variableStyle.ParameterStyle.ParameterBehavior != ParameterBehavior.None) || - (_useAsReturnValue && _variableStyle.ReturnStyle.ParameterBehavior != ParameterBehavior.None); + get + { + return _variableStyle.ReturnStyle.ReturnBehavior != ReturnBehavior.None; + } } - } - public ParameterBehavior ParameterModifier - { - get + public bool UseAsParameter { - return _useAsReturnValue ? _variableStyle.ReturnStyle.ParameterBehavior : _variableStyle.ParameterStyle.ParameterBehavior; + get + { + return (!_useAsReturnValue && _variableStyle.ParameterStyle.ParameterBehavior != ParameterBehavior.None) || + (_useAsReturnValue && _variableStyle.ReturnStyle.ParameterBehavior != ParameterBehavior.None); + } } - } - public DeclarationBehavior GetDeclarationBehavior(CancellationToken cancellationToken) - { - if (_useAsReturnValue) + public ParameterBehavior ParameterModifier { - return _variableStyle.ReturnStyle.DeclarationBehavior; + get + { + return _useAsReturnValue ? _variableStyle.ReturnStyle.ParameterBehavior : _variableStyle.ParameterStyle.ParameterBehavior; + } } - if (_variableSymbol.GetUseSaferDeclarationBehavior(cancellationToken)) + public DeclarationBehavior GetDeclarationBehavior(CancellationToken cancellationToken) { - return _variableStyle.ParameterStyle.SaferDeclarationBehavior; - } + if (_useAsReturnValue) + { + return _variableStyle.ReturnStyle.DeclarationBehavior; + } - return _variableStyle.ParameterStyle.DeclarationBehavior; - } + if (_variableSymbol.GetUseSaferDeclarationBehavior(cancellationToken)) + { + return _variableStyle.ParameterStyle.SaferDeclarationBehavior; + } - public ReturnBehavior ReturnBehavior - { - get + return _variableStyle.ParameterStyle.DeclarationBehavior; + } + + public ReturnBehavior ReturnBehavior { - if (_useAsReturnValue) + get { - return _variableStyle.ReturnStyle.ReturnBehavior; - } + if (_useAsReturnValue) + { + return _variableStyle.ReturnStyle.ReturnBehavior; + } - return ReturnBehavior.None; + return ReturnBehavior.None; + } } - } - public static VariableInfo CreateReturnValue(VariableInfo variable) - { - Contract.ThrowIfNull(variable); - Contract.ThrowIfFalse(variable.CanBeUsedAsReturnValue); - Contract.ThrowIfFalse(variable.ParameterModifier is ParameterBehavior.Out or ParameterBehavior.Ref); + public static VariableInfo CreateReturnValue(VariableInfo variable) + { + Contract.ThrowIfNull(variable); + Contract.ThrowIfFalse(variable.CanBeUsedAsReturnValue); + Contract.ThrowIfFalse(variable.ParameterModifier is ParameterBehavior.Out or ParameterBehavior.Ref); - return new VariableInfo(variable._variableSymbol, variable._variableStyle, useAsReturnValue: true); - } + return new VariableInfo(variable._variableSymbol, variable._variableStyle, useAsReturnValue: true); + } - public void AddIdentifierTokenAnnotationPair( - List<(SyntaxToken, SyntaxAnnotation)> annotations, CancellationToken cancellationToken) - { - _variableSymbol.AddIdentifierTokenAnnotationPair(annotations, cancellationToken); - } + public void AddIdentifierTokenAnnotationPair( + List<(SyntaxToken, SyntaxAnnotation)> annotations, CancellationToken cancellationToken) + { + _variableSymbol.AddIdentifierTokenAnnotationPair(annotations, cancellationToken); + } - public string Name => _variableSymbol.Name; + public string Name => _variableSymbol.Name; - /// - /// Returns true, if the variable could be either passed as a parameter - /// to the new local function or the local function can capture the variable. - /// - public bool CanBeCapturedByLocalFunction - => _variableSymbol.CanBeCapturedByLocalFunction; + /// + /// Returns true, if the variable could be either passed as a parameter + /// to the new local function or the local function can capture the variable. + /// + public bool CanBeCapturedByLocalFunction + => _variableSymbol.CanBeCapturedByLocalFunction; - public bool OriginalTypeHadAnonymousTypeOrDelegate => _variableSymbol.OriginalTypeHadAnonymousTypeOrDelegate; + public bool OriginalTypeHadAnonymousTypeOrDelegate => _variableSymbol.OriginalTypeHadAnonymousTypeOrDelegate; - public ITypeSymbol OriginalType => _variableSymbol.OriginalType; + public ITypeSymbol OriginalType => _variableSymbol.OriginalType; - public ITypeSymbol GetVariableType() - => _variableSymbol.OriginalType; + public ITypeSymbol GetVariableType() + => _variableSymbol.OriginalType; - public SyntaxToken GetIdentifierTokenAtDeclaration(SemanticDocument document) - => document.GetTokenWithAnnotation(_variableSymbol.IdentifierTokenAnnotation); + public SyntaxToken GetIdentifierTokenAtDeclaration(SemanticDocument document) + => document.GetTokenWithAnnotation(_variableSymbol.IdentifierTokenAnnotation); - public SyntaxToken GetIdentifierTokenAtDeclaration(SyntaxNode node) - => node.GetAnnotatedTokens(_variableSymbol.IdentifierTokenAnnotation).SingleOrDefault(); + public SyntaxToken GetIdentifierTokenAtDeclaration(SyntaxNode node) + => node.GetAnnotatedTokens(_variableSymbol.IdentifierTokenAnnotation).SingleOrDefault(); - public SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken) => _variableSymbol.GetOriginalIdentifierToken(cancellationToken); + public SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken) => _variableSymbol.GetOriginalIdentifierToken(cancellationToken); - public static void SortVariables(ArrayBuilder variables) - => variables.Sort(); + public static void SortVariables(ArrayBuilder variables) + => variables.Sort(); - public int CompareTo(VariableInfo other) - => VariableSymbol.Compare(this._variableSymbol, other._variableSymbol); + public int CompareTo(VariableInfo other) + => VariableSymbol.Compare(this._variableSymbol, other._variableSymbol); + } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableSymbol.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableSymbol.cs index 974e97b436318..b0317fd8292f7 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableSymbol.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableSymbol.cs @@ -14,342 +14,354 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - /// - /// temporary symbol until we have a symbol that can hold onto both local and parameter symbol - /// - protected abstract class VariableSymbol + internal abstract partial class MethodExtractor { - protected VariableSymbol(Compilation compilation, ITypeSymbol type) - { - OriginalTypeHadAnonymousTypeOrDelegate = type.ContainsAnonymousType(); - OriginalType = OriginalTypeHadAnonymousTypeOrDelegate ? type.RemoveAnonymousTypes(compilation) : type; - } - - public abstract int DisplayOrder { get; } - public abstract string Name { get; } - public abstract bool CanBeCapturedByLocalFunction { get; } - - public abstract bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken); - public abstract SyntaxAnnotation IdentifierTokenAnnotation { get; } - public abstract SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken); - - public abstract void AddIdentifierTokenAnnotationPair( - List<(SyntaxToken, SyntaxAnnotation)> annotations, CancellationToken cancellationToken); - - protected abstract int CompareTo(VariableSymbol right); - - /// - /// return true if original type had anonymous type or delegate somewhere in the type - /// - public bool OriginalTypeHadAnonymousTypeOrDelegate { get; } - /// - /// get the original type with anonymous type removed + /// temporary symbol until we have a symbol that can hold onto both local and parameter symbol /// - public ITypeSymbol OriginalType { get; } - - public static int Compare(VariableSymbol left, VariableSymbol right) + protected abstract class VariableSymbol { - // CancellationTokens always go at the end of method signature. - var leftIsCancellationToken = IsCancellationToken(left.OriginalType); - var rightIsCancellationToken = IsCancellationToken(right.OriginalType); - - if (leftIsCancellationToken && !rightIsCancellationToken) - { - return 1; - } - else if (!leftIsCancellationToken && rightIsCancellationToken) - { - return -1; - } + /// + /// return true if original type had anonymous type or delegate somewhere in the type + /// + public bool OriginalTypeHadAnonymousTypeOrDelegate { get; } - if (left.DisplayOrder == right.DisplayOrder) - { - return left.CompareTo(right); - } + /// + /// get the original type with anonymous type removed + /// + public ITypeSymbol OriginalType { get; } - return left.DisplayOrder - right.DisplayOrder; - } + private readonly bool _isCancellationToken; - private static bool IsCancellationToken(ITypeSymbol originalType) - { - return originalType is + protected VariableSymbol(ITypeSymbol type) { - Name: nameof(CancellationToken), - ContainingNamespace: + OriginalTypeHadAnonymousTypeOrDelegate = type.ContainsAnonymousType(); + OriginalType = type; + _isCancellationToken = IsCancellationToken(OriginalType); + + static bool IsCancellationToken(ITypeSymbol originalType) { - Name: nameof(System.Threading), - ContainingNamespace: + return originalType is { - Name: nameof(System), - ContainingNamespace.IsGlobalNamespace: true, - } + Name: nameof(CancellationToken), + ContainingNamespace: + { + Name: nameof(System.Threading), + ContainingNamespace: + { + Name: nameof(System), + ContainingNamespace.IsGlobalNamespace: true, + } + } + }; } - }; - } - } + } - protected abstract class NotMovableVariableSymbol(Compilation compilation, ITypeSymbol type) : VariableSymbol(compilation, type) - { - public override bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken) - { - // decl never get moved - return false; - } + public abstract int DisplayOrder { get; } + public abstract string Name { get; } + public abstract bool CanBeCapturedByLocalFunction { get; } - public override SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken) - => default; + public abstract bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken); + public abstract SyntaxAnnotation IdentifierTokenAnnotation { get; } + public abstract SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken); - public override SyntaxAnnotation IdentifierTokenAnnotation => throw ExceptionUtilities.Unreachable(); + public abstract void AddIdentifierTokenAnnotationPair( + List<(SyntaxToken, SyntaxAnnotation)> annotations, CancellationToken cancellationToken); - public override void AddIdentifierTokenAnnotationPair( - List<(SyntaxToken, SyntaxAnnotation)> annotations, CancellationToken cancellationToken) - { - // do nothing for parameter - } - } + protected abstract int CompareTo(VariableSymbol right); - protected class ParameterVariableSymbol : NotMovableVariableSymbol, IComparable - { - private readonly IParameterSymbol _parameterSymbol; + public static int Compare(VariableSymbol left, VariableSymbol right) + { + // CancellationTokens always go at the end of method signature. + var leftIsCancellationToken = left._isCancellationToken; + var rightIsCancellationToken = right._isCancellationToken; - public ParameterVariableSymbol(Compilation compilation, IParameterSymbol parameterSymbol, ITypeSymbol type) - : base(compilation, type) - { - Contract.ThrowIfNull(parameterSymbol); - _parameterSymbol = parameterSymbol; - } + if (leftIsCancellationToken && !rightIsCancellationToken) + { + return 1; + } + else if (!leftIsCancellationToken && rightIsCancellationToken) + { + return -1; + } - public override int DisplayOrder => 0; + if (left.DisplayOrder == right.DisplayOrder) + { + return left.CompareTo(right); + } - protected override int CompareTo(VariableSymbol right) - => CompareTo((ParameterVariableSymbol)right); + return left.DisplayOrder - right.DisplayOrder; + } + } - public int CompareTo(ParameterVariableSymbol other) + protected abstract class NotMovableVariableSymbol(ITypeSymbol type) : VariableSymbol(type) { - Contract.ThrowIfNull(other); - - if (this == other) + public override bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken) { - return 0; + // decl never get moved + return false; } - var compare = CompareMethodParameters((IMethodSymbol)_parameterSymbol.ContainingSymbol, (IMethodSymbol)other._parameterSymbol.ContainingSymbol); - if (compare != 0) + public override SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken) + => default; + + public override SyntaxAnnotation IdentifierTokenAnnotation => throw ExceptionUtilities.Unreachable(); + + public override void AddIdentifierTokenAnnotationPair( + List<(SyntaxToken, SyntaxAnnotation)> annotations, CancellationToken cancellationToken) { - return compare; + // do nothing for parameter } - - Contract.ThrowIfFalse(_parameterSymbol.Ordinal != other._parameterSymbol.Ordinal); - return (_parameterSymbol.Ordinal > other._parameterSymbol.Ordinal) ? 1 : -1; } - private static int CompareMethodParameters(IMethodSymbol left, IMethodSymbol right) + protected sealed class ParameterVariableSymbol : NotMovableVariableSymbol, IComparable { - if (left == null && right == null) - { - // not method parameters - return 0; - } + private readonly IParameterSymbol _parameterSymbol; - if (left.Equals(right)) + public ParameterVariableSymbol(IParameterSymbol parameterSymbol, ITypeSymbol type) + : base(type) { - // parameter of same method - return 0; + Contract.ThrowIfNull(parameterSymbol); + _parameterSymbol = parameterSymbol; } - // these methods can be either regular one, anonymous function, local function and etc - // but all must belong to same outer regular method. - // so, it should have location pointing to same tree - var leftLocations = left.Locations; - var rightLocations = right.Locations; + public override int DisplayOrder => 0; - var commonTree = leftLocations.Select(l => l.SourceTree).Intersect(rightLocations.Select(l => l.SourceTree)).WhereNotNull().First(); + protected override int CompareTo(VariableSymbol right) + => CompareTo((ParameterVariableSymbol)right); - var leftLocation = leftLocations.First(l => l.SourceTree == commonTree); - var rightLocation = rightLocations.First(l => l.SourceTree == commonTree); + public int CompareTo(ParameterVariableSymbol other) + { + Contract.ThrowIfNull(other); - return leftLocation.SourceSpan.Start - rightLocation.SourceSpan.Start; - } + if (this == other) + { + return 0; + } - public override string Name - { - get - { - return _parameterSymbol.ToDisplayString( - new SymbolDisplayFormat( - parameterOptions: SymbolDisplayParameterOptions.IncludeName, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); - } - } + var compare = CompareMethodParameters((IMethodSymbol)_parameterSymbol.ContainingSymbol, (IMethodSymbol)other._parameterSymbol.ContainingSymbol); + if (compare != 0) + { + return compare; + } - public override bool CanBeCapturedByLocalFunction => true; - } + Contract.ThrowIfFalse(_parameterSymbol.Ordinal != other._parameterSymbol.Ordinal); + return (_parameterSymbol.Ordinal > other._parameterSymbol.Ordinal) ? 1 : -1; + } - protected class LocalVariableSymbol : VariableSymbol, IComparable> where T : SyntaxNode - { - private readonly SyntaxAnnotation _annotation = new(); - private readonly ILocalSymbol _localSymbol; - private readonly HashSet _nonNoisySet; + private static int CompareMethodParameters(IMethodSymbol left, IMethodSymbol right) + { + if (left == null && right == null) + { + // not method parameters + return 0; + } - public LocalVariableSymbol(Compilation compilation, ILocalSymbol localSymbol, ITypeSymbol type, HashSet nonNoisySet) - : base(compilation, type) - { - Contract.ThrowIfNull(localSymbol); - Contract.ThrowIfNull(nonNoisySet); + if (left.Equals(right)) + { + // parameter of same method + return 0; + } - _localSymbol = localSymbol; - _nonNoisySet = nonNoisySet; - } + // these methods can be either regular one, anonymous function, local function and etc + // but all must belong to same outer regular method. + // so, it should have location pointing to same tree + var leftLocations = left.Locations; + var rightLocations = right.Locations; - public override int DisplayOrder => 1; + var commonTree = leftLocations.Select(l => l.SourceTree).Intersect(rightLocations.Select(l => l.SourceTree)).WhereNotNull().First(); - protected override int CompareTo(VariableSymbol right) - => CompareTo((LocalVariableSymbol)right); + var leftLocation = leftLocations.First(l => l.SourceTree == commonTree); + var rightLocation = rightLocations.First(l => l.SourceTree == commonTree); - public int CompareTo(LocalVariableSymbol other) - { - Contract.ThrowIfNull(other); + return leftLocation.SourceSpan.Start - rightLocation.SourceSpan.Start; + } - if (this == other) + public override string Name { - return 0; + get + { + return _parameterSymbol.ToDisplayString( + new SymbolDisplayFormat( + parameterOptions: SymbolDisplayParameterOptions.IncludeName, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); + } } - Contract.ThrowIfFalse(_localSymbol.Locations.Length == 1); - Contract.ThrowIfFalse(other._localSymbol.Locations.Length == 1); - Contract.ThrowIfFalse(_localSymbol.Locations[0].IsInSource); - Contract.ThrowIfFalse(other._localSymbol.Locations[0].IsInSource); - Contract.ThrowIfFalse(_localSymbol.Locations[0].SourceTree == other._localSymbol.Locations[0].SourceTree); - Contract.ThrowIfFalse(_localSymbol.Locations[0].SourceSpan.Start != other._localSymbol.Locations[0].SourceSpan.Start); - - return _localSymbol.Locations[0].SourceSpan.Start - other._localSymbol.Locations[0].SourceSpan.Start; + public override bool CanBeCapturedByLocalFunction => true; } - public override string Name + protected sealed class LocalVariableSymbol : VariableSymbol, IComparable { - get - { - return _localSymbol.ToDisplayString( - new SymbolDisplayFormat( - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); - } - } + private readonly SyntaxAnnotation _annotation = new(); + private readonly ILocalSymbol _localSymbol; + private readonly HashSet _nonNoisySet; - public override SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(_localSymbol.Locations.Length == 1); - Contract.ThrowIfFalse(_localSymbol.Locations[0].IsInSource); - Contract.ThrowIfNull(_localSymbol.Locations[0].SourceTree); + public LocalVariableSymbol(ILocalSymbol localSymbol, ITypeSymbol type, HashSet nonNoisySet) + : base(type) + { + Contract.ThrowIfNull(localSymbol); + Contract.ThrowIfNull(nonNoisySet); - var tree = _localSymbol.Locations[0].SourceTree; - var span = _localSymbol.Locations[0].SourceSpan; + _localSymbol = localSymbol; + _nonNoisySet = nonNoisySet; + } - var token = tree.GetRoot(cancellationToken).FindToken(span.Start); - Contract.ThrowIfFalse(token.Span.Equals(span)); + public override int DisplayOrder => 1; - return token; - } + protected override int CompareTo(VariableSymbol right) + => CompareTo((LocalVariableSymbol)right); - public override SyntaxAnnotation IdentifierTokenAnnotation => _annotation; + public int CompareTo(LocalVariableSymbol other) + { + Contract.ThrowIfNull(other); - public override bool CanBeCapturedByLocalFunction => true; + if (this == other) + { + return 0; + } - public override void AddIdentifierTokenAnnotationPair( - List<(SyntaxToken, SyntaxAnnotation)> annotations, CancellationToken cancellationToken) - { - annotations.Add((GetOriginalIdentifierToken(cancellationToken), _annotation)); - } + Contract.ThrowIfFalse(_localSymbol.Locations.Length == 1); + Contract.ThrowIfFalse(other._localSymbol.Locations.Length == 1); + Contract.ThrowIfFalse(_localSymbol.Locations[0].IsInSource); + Contract.ThrowIfFalse(other._localSymbol.Locations[0].IsInSource); + Contract.ThrowIfFalse(_localSymbol.Locations[0].SourceTree == other._localSymbol.Locations[0].SourceTree); + Contract.ThrowIfFalse(_localSymbol.Locations[0].SourceSpan.Start != other._localSymbol.Locations[0].SourceSpan.Start); - public override bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken) - { - var identifier = GetOriginalIdentifierToken(cancellationToken); + return _localSymbol.Locations[0].SourceSpan.Start - other._localSymbol.Locations[0].SourceSpan.Start; + } - // check whether there is a noisy trivia around the token. - if (ContainsNoisyTrivia(identifier.LeadingTrivia)) + public override string Name { - return true; + get + { + return _localSymbol.ToDisplayString( + new SymbolDisplayFormat( + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); + } } - if (ContainsNoisyTrivia(identifier.TrailingTrivia)) + public override SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken) { - return true; + Contract.ThrowIfFalse(_localSymbol.Locations.Length == 1); + Contract.ThrowIfFalse(_localSymbol.Locations[0].IsInSource); + Contract.ThrowIfNull(_localSymbol.Locations[0].SourceTree); + + var tree = _localSymbol.Locations[0].SourceTree; + var span = _localSymbol.Locations[0].SourceSpan; + + var token = tree.GetRoot(cancellationToken).FindToken(span.Start); + Contract.ThrowIfFalse(token.Span.Equals(span)); + + return token; } - var declStatement = identifier.Parent.FirstAncestorOrSelf(); - if (declStatement == null) + public override SyntaxAnnotation IdentifierTokenAnnotation => _annotation; + + public override bool CanBeCapturedByLocalFunction => true; + + public override void AddIdentifierTokenAnnotationPair( + List<(SyntaxToken, SyntaxAnnotation)> annotations, CancellationToken cancellationToken) { - return true; + annotations.Add((GetOriginalIdentifierToken(cancellationToken), _annotation)); } - foreach (var token in declStatement.DescendantTokens()) + public override bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken) { - if (ContainsNoisyTrivia(token.LeadingTrivia)) + var identifier = GetOriginalIdentifierToken(cancellationToken); + + // check whether there is a noisy trivia around the token. + if (ContainsNoisyTrivia(identifier.LeadingTrivia)) { return true; } - if (ContainsNoisyTrivia(token.TrailingTrivia)) + if (ContainsNoisyTrivia(identifier.TrailingTrivia)) { return true; } - } - - return false; - } - private bool ContainsNoisyTrivia(SyntaxTriviaList list) - => list.Any(t => !_nonNoisySet.Contains(t.RawKind)); - } + var declStatement = identifier.Parent.FirstAncestorOrSelf(); + if (declStatement == null) + { + return true; + } - protected class QueryVariableSymbol : NotMovableVariableSymbol, IComparable - { - private readonly IRangeVariableSymbol _symbol; + foreach (var token in declStatement.DescendantTokens()) + { + if (ContainsNoisyTrivia(token.LeadingTrivia)) + { + return true; + } - public QueryVariableSymbol(Compilation compilation, IRangeVariableSymbol symbol, ITypeSymbol type) - : base(compilation, type) - { - Contract.ThrowIfNull(symbol); - _symbol = symbol; - } + if (ContainsNoisyTrivia(token.TrailingTrivia)) + { + return true; + } + } - public override int DisplayOrder => 2; + return false; + } - protected override int CompareTo(VariableSymbol right) - => CompareTo((QueryVariableSymbol)right); + private bool ContainsNoisyTrivia(SyntaxTriviaList list) + => list.Any(t => !_nonNoisySet.Contains(t.RawKind)); + } - public int CompareTo(QueryVariableSymbol other) + protected sealed class QueryVariableSymbol : NotMovableVariableSymbol, IComparable { - Contract.ThrowIfNull(other); + private readonly IRangeVariableSymbol _symbol; - if (this == other) + public QueryVariableSymbol(IRangeVariableSymbol symbol, ITypeSymbol type) + : base(type) { - return 0; + Contract.ThrowIfNull(symbol); + _symbol = symbol; } - var locationLeft = _symbol.Locations.First(); - var locationRight = other._symbol.Locations.First(); + public override int DisplayOrder => 2; - Contract.ThrowIfFalse(locationLeft.IsInSource); - Contract.ThrowIfFalse(locationRight.IsInSource); - Contract.ThrowIfFalse(locationLeft.SourceTree == locationRight.SourceTree); - Contract.ThrowIfFalse(locationLeft.SourceSpan.Start != locationRight.SourceSpan.Start); + protected override int CompareTo(VariableSymbol right) + => CompareTo((QueryVariableSymbol)right); - return locationLeft.SourceSpan.Start - locationRight.SourceSpan.Start; - } + public int CompareTo(QueryVariableSymbol other) + { + Contract.ThrowIfNull(other); - public override string Name - { - get + if (this == other) + { + return 0; + } + + var locationLeft = _symbol.Locations.First(); + var locationRight = other._symbol.Locations.First(); + + Contract.ThrowIfFalse(locationLeft.IsInSource); + Contract.ThrowIfFalse(locationRight.IsInSource); + Contract.ThrowIfFalse(locationLeft.SourceTree == locationRight.SourceTree); + Contract.ThrowIfFalse(locationLeft.SourceSpan.Start != locationRight.SourceSpan.Start); + + return locationLeft.SourceSpan.Start - locationRight.SourceSpan.Start; + } + + public override string Name { - return _symbol.ToDisplayString( - new SymbolDisplayFormat( - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); + get + { + return _symbol.ToDisplayString( + new SymbolDisplayFormat( + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); + } } - } - public override bool CanBeCapturedByLocalFunction => false; + public override bool CanBeCapturedByLocalFunction => false; + } } } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs index 78714487bce42..bdf4a4ea4205e 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs @@ -19,273 +19,276 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class MethodExtractor< +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, TSelectionResult, TStatementSyntax, - TExpressionSyntax>( + TExecutableStatementSyntax, + TExpressionSyntax> +{ + internal abstract partial class MethodExtractor( TSelectionResult selectionResult, ExtractMethodGenerationOptions options, bool localFunction) - where TSelectionResult : SelectionResult - where TStatementSyntax : SyntaxNode - where TExpressionSyntax : SyntaxNode -{ - protected readonly TSelectionResult OriginalSelectionResult = selectionResult; - protected readonly ExtractMethodGenerationOptions Options = options; - protected readonly bool LocalFunction = localFunction; + { + protected readonly TSelectionResult OriginalSelectionResult = selectionResult; + protected readonly ExtractMethodGenerationOptions Options = options; + protected readonly bool LocalFunction = localFunction; - protected abstract SyntaxNode ParseTypeName(string name); - protected abstract AnalyzerResult Analyze(TSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken); - protected abstract SyntaxNode GetInsertionPointNode(AnalyzerResult analyzerResult, CancellationToken cancellationToken); - protected abstract Task PreserveTriviaAsync(TSelectionResult selectionResult, CancellationToken cancellationToken); + protected abstract SyntaxNode ParseTypeName(string name); + protected abstract AnalyzerResult Analyze(TSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken); + protected abstract SyntaxNode GetInsertionPointNode(AnalyzerResult analyzerResult, CancellationToken cancellationToken); + protected abstract Task PreserveTriviaAsync(TSelectionResult selectionResult, CancellationToken cancellationToken); - protected abstract CodeGenerator CreateCodeGenerator(AnalyzerResult analyzerResult); - protected abstract Task GenerateCodeAsync( - InsertionPoint insertionPoint, TSelectionResult selectionResult, AnalyzerResult analyzeResult, ExtractMethodGenerationOptions options, CancellationToken cancellationToken); + protected abstract CodeGenerator CreateCodeGenerator(AnalyzerResult analyzerResult); + protected abstract Task GenerateCodeAsync( + InsertionPoint insertionPoint, TSelectionResult selectionResult, AnalyzerResult analyzeResult, ExtractMethodGenerationOptions options, CancellationToken cancellationToken); - protected abstract SyntaxToken? GetInvocationNameToken(IEnumerable tokens); - protected abstract AbstractFormattingRule GetCustomFormattingRule(Document document); + protected abstract SyntaxToken? GetInvocationNameToken(IEnumerable tokens); + protected abstract AbstractFormattingRule GetCustomFormattingRule(Document document); - protected abstract Task<(Document document, SyntaxToken? invocationNameToken)> InsertNewLineBeforeLocalFunctionIfNecessaryAsync( - Document document, SyntaxToken? invocationNameToken, SyntaxNode methodDefinition, CancellationToken cancellationToken); + protected abstract Task<(Document document, SyntaxToken? invocationNameToken)> InsertNewLineBeforeLocalFunctionIfNecessaryAsync( + Document document, SyntaxToken? invocationNameToken, SyntaxNode methodDefinition, CancellationToken cancellationToken); - public ExtractMethodResult ExtractMethod(OperationStatus initialStatus, CancellationToken cancellationToken) - { - var originalSemanticDocument = OriginalSelectionResult.SemanticDocument; - var analyzeResult = Analyze(OriginalSelectionResult, LocalFunction, cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); + public ExtractMethodResult ExtractMethod(OperationStatus initialStatus, CancellationToken cancellationToken) + { + var originalSemanticDocument = OriginalSelectionResult.SemanticDocument; + var analyzeResult = Analyze(OriginalSelectionResult, LocalFunction, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); - var status = CheckVariableTypes(analyzeResult.Status.With(initialStatus), analyzeResult, cancellationToken); - if (status.Failed) - return ExtractMethodResult.Fail(status); + var status = CheckVariableTypes(analyzeResult.Status.With(initialStatus), analyzeResult, cancellationToken); + if (status.Failed) + return ExtractMethodResult.Fail(status); - var insertionPointNode = GetInsertionPointNode(analyzeResult, cancellationToken); + var insertionPointNode = GetInsertionPointNode(analyzeResult, cancellationToken); - if (!CanAddTo(originalSemanticDocument.Document, insertionPointNode, out var canAddStatus)) - return ExtractMethodResult.Fail(canAddStatus); + if (!CanAddTo(originalSemanticDocument.Document, insertionPointNode, out var canAddStatus)) + return ExtractMethodResult.Fail(canAddStatus); - cancellationToken.ThrowIfCancellationRequested(); - var codeGenerator = this.CreateCodeGenerator(analyzeResult); + cancellationToken.ThrowIfCancellationRequested(); + var codeGenerator = this.CreateCodeGenerator(analyzeResult); - var statements = codeGenerator.GetNewMethodStatements(insertionPointNode, cancellationToken); - if (statements.Status.Failed) - return ExtractMethodResult.Fail(statements.Status); + var statements = codeGenerator.GetNewMethodStatements(insertionPointNode, cancellationToken); + if (statements.Status.Failed) + return ExtractMethodResult.Fail(statements.Status); - return ExtractMethodResult.Success( - status, - async cancellationToken => - { - var (analyzedDocument, insertionPoint) = await GetAnnotatedDocumentAndInsertionPointAsync( - originalSemanticDocument, analyzeResult, insertionPointNode, cancellationToken).ConfigureAwait(false); + return ExtractMethodResult.Success( + status, + async cancellationToken => + { + var (analyzedDocument, insertionPoint) = await GetAnnotatedDocumentAndInsertionPointAsync( + originalSemanticDocument, analyzeResult, insertionPointNode, cancellationToken).ConfigureAwait(false); - var triviaResult = await PreserveTriviaAsync((TSelectionResult)OriginalSelectionResult.With(analyzedDocument), cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + var triviaResult = await PreserveTriviaAsync((TSelectionResult)OriginalSelectionResult.With(analyzedDocument), cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - var generatedCode = await GenerateCodeAsync( - insertionPoint.With(triviaResult.SemanticDocument), - (TSelectionResult)OriginalSelectionResult.With(triviaResult.SemanticDocument), - analyzeResult, - Options, - cancellationToken).ConfigureAwait(false); + var generatedCode = await GenerateCodeAsync( + insertionPoint.With(triviaResult.SemanticDocument), + (TSelectionResult)OriginalSelectionResult.With(triviaResult.SemanticDocument), + analyzeResult, + Options, + cancellationToken).ConfigureAwait(false); - var afterTriviaRestored = await triviaResult.ApplyAsync(generatedCode, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + var afterTriviaRestored = await triviaResult.ApplyAsync(generatedCode, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - var documentWithoutFinalFormatting = afterTriviaRestored.Document; + var documentWithoutFinalFormatting = afterTriviaRestored.Document; - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - var newRoot = afterTriviaRestored.Root; - var invocationNameToken = GetInvocationNameToken(newRoot.GetAnnotatedTokens(generatedCode.MethodNameAnnotation)); + var newRoot = afterTriviaRestored.Root; + var invocationNameToken = GetInvocationNameToken(newRoot.GetAnnotatedTokens(generatedCode.MethodNameAnnotation)); - // Do some final patchups of whitespace when inserting a local function. - if (LocalFunction) - { - var methodDefinition = newRoot.GetAnnotatedNodesAndTokens(generatedCode.MethodDefinitionAnnotation).FirstOrDefault().AsNode(); - (documentWithoutFinalFormatting, invocationNameToken) = await InsertNewLineBeforeLocalFunctionIfNecessaryAsync( - documentWithoutFinalFormatting, invocationNameToken, methodDefinition, cancellationToken).ConfigureAwait(false); - } - - return await GetFormattedDocumentAsync( - documentWithoutFinalFormatting, invocationNameToken, cancellationToken).ConfigureAwait(false); - }); + // Do some final patchups of whitespace when inserting a local function. + if (LocalFunction) + { + var methodDefinition = newRoot.GetAnnotatedNodesAndTokens(generatedCode.MethodDefinitionAnnotation).FirstOrDefault().AsNode(); + (documentWithoutFinalFormatting, invocationNameToken) = await InsertNewLineBeforeLocalFunctionIfNecessaryAsync( + documentWithoutFinalFormatting, invocationNameToken, methodDefinition, cancellationToken).ConfigureAwait(false); + } - bool CanAddTo(Document document, SyntaxNode insertionPointNode, out OperationStatus status) - { - var syntaxFacts = document.GetLanguageService(); - var syntaxKinds = syntaxFacts.SyntaxKinds; - var codeGenService = document.GetLanguageService(); + return await GetFormattedDocumentAsync( + documentWithoutFinalFormatting, invocationNameToken, cancellationToken).ConfigureAwait(false); + }); - if (insertionPointNode is null) + bool CanAddTo(Document document, SyntaxNode insertionPointNode, out OperationStatus status) { - status = OperationStatus.NoValidLocationToInsertMethodCall; - return false; - } + var syntaxFacts = document.GetLanguageService(); + var syntaxKinds = syntaxFacts.SyntaxKinds; + var codeGenService = document.GetLanguageService(); - var destination = insertionPointNode; - if (!LocalFunction) - { - var mappedPoint = insertionPointNode.RawKind == syntaxKinds.GlobalStatement - ? insertionPointNode.Parent - : insertionPointNode; - destination = mappedPoint.Parent ?? mappedPoint; - } + if (insertionPointNode is null) + { + status = OperationStatus.NoValidLocationToInsertMethodCall; + return false; + } - if (!codeGenService.CanAddTo(destination, document.Project.Solution, cancellationToken)) - { - status = OperationStatus.OverlapsHiddenPosition; - return false; - } + var destination = insertionPointNode; + if (!LocalFunction) + { + var mappedPoint = insertionPointNode.RawKind == syntaxKinds.GlobalStatement + ? insertionPointNode.Parent + : insertionPointNode; + destination = mappedPoint.Parent ?? mappedPoint; + } + + if (!codeGenService.CanAddTo(destination, document.Project.Solution, cancellationToken)) + { + status = OperationStatus.OverlapsHiddenPosition; + return false; + } - status = OperationStatus.SucceededStatus; - return true; + status = OperationStatus.SucceededStatus; + return true; + } } - } - private async Task<(Document document, SyntaxToken? invocationNameToken)> GetFormattedDocumentAsync( - Document document, - SyntaxToken? invocationNameToken, - CancellationToken cancellationToken) - { - var annotation = new SyntaxAnnotation(); + private async Task<(Document document, SyntaxToken? invocationNameToken)> GetFormattedDocumentAsync( + Document document, + SyntaxToken? invocationNameToken, + CancellationToken cancellationToken) + { + var annotation = new SyntaxAnnotation(); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - if (invocationNameToken != null) - root = root.ReplaceToken(invocationNameToken.Value, invocationNameToken.Value.WithAdditionalAnnotations(annotation)); + if (invocationNameToken != null) + root = root.ReplaceToken(invocationNameToken.Value, invocationNameToken.Value.WithAdditionalAnnotations(annotation)); - var annotatedDocument = document.WithSyntaxRoot(root); - var simplifiedDocument = await Simplifier.ReduceAsync(annotatedDocument, Simplifier.Annotation, this.Options.CodeCleanupOptions.SimplifierOptions, cancellationToken).ConfigureAwait(false); - var simplifiedRoot = await simplifiedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var annotatedDocument = document.WithSyntaxRoot(root); + var simplifiedDocument = await Simplifier.ReduceAsync(annotatedDocument, Simplifier.Annotation, this.Options.CodeCleanupOptions.SimplifierOptions, cancellationToken).ConfigureAwait(false); + var simplifiedRoot = await simplifiedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var services = document.Project.Solution.Services; + var services = document.Project.Solution.Services; - var formattingRules = GetFormattingRules(document); - var formattedDocument = simplifiedDocument.WithSyntaxRoot( - Formatter.Format(simplifiedRoot, Formatter.Annotation, services, this.Options.CodeCleanupOptions.FormattingOptions, formattingRules, cancellationToken)); + var formattingRules = GetFormattingRules(document); + var formattedDocument = simplifiedDocument.WithSyntaxRoot( + Formatter.Format(simplifiedRoot, Formatter.Annotation, services, this.Options.CodeCleanupOptions.FormattingOptions, formattingRules, cancellationToken)); - var formattedRoot = await formattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var finalInvocationNameToken = formattedRoot.GetAnnotatedTokens(annotation).SingleOrDefault(); - return (formattedDocument, finalInvocationNameToken == default ? null : finalInvocationNameToken); - } + var formattedRoot = await formattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var finalInvocationNameToken = formattedRoot.GetAnnotatedTokens(annotation).SingleOrDefault(); + return (formattedDocument, finalInvocationNameToken == default ? null : finalInvocationNameToken); + } - private static async Task<(SemanticDocument analyzedDocument, InsertionPoint insertionPoint)> GetAnnotatedDocumentAndInsertionPointAsync( - SemanticDocument document, - AnalyzerResult analyzeResult, - SyntaxNode insertionPointNode, - CancellationToken cancellationToken) - { - var annotations = new List<(SyntaxToken, SyntaxAnnotation)>(analyzeResult.Variables.Length); - foreach (var variable in analyzeResult.Variables) - variable.AddIdentifierTokenAnnotationPair(annotations, cancellationToken); + private static async Task<(SemanticDocument analyzedDocument, InsertionPoint insertionPoint)> GetAnnotatedDocumentAndInsertionPointAsync( + SemanticDocument document, + AnalyzerResult analyzeResult, + SyntaxNode insertionPointNode, + CancellationToken cancellationToken) + { + var annotations = new List<(SyntaxToken, SyntaxAnnotation)>(analyzeResult.Variables.Length); + foreach (var variable in analyzeResult.Variables) + variable.AddIdentifierTokenAnnotationPair(annotations, cancellationToken); - var tokenMap = annotations.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); + var tokenMap = annotations.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); - var insertionPointAnnotation = new SyntaxAnnotation(); + var insertionPointAnnotation = new SyntaxAnnotation(); - var finalRoot = document.Root.ReplaceSyntax( - nodes: [insertionPointNode], - // intentionally using 'n' (new) here. We want to see any updated sub tokens that were updated in computeReplacementToken - computeReplacementNode: (o, n) => n.WithAdditionalAnnotations(insertionPointAnnotation), - tokens: tokenMap.Keys, - computeReplacementToken: (o, n) => o.WithAdditionalAnnotations(tokenMap[o]), - trivia: null, - computeReplacementTrivia: null); + var finalRoot = document.Root.ReplaceSyntax( + nodes: [insertionPointNode], + // intentionally using 'n' (new) here. We want to see any updated sub tokens that were updated in computeReplacementToken + computeReplacementNode: (o, n) => n.WithAdditionalAnnotations(insertionPointAnnotation), + tokens: tokenMap.Keys, + computeReplacementToken: (o, n) => o.WithAdditionalAnnotations(tokenMap[o]), + trivia: null, + computeReplacementTrivia: null); - var finalDocument = await document.WithSyntaxRootAsync(finalRoot, cancellationToken).ConfigureAwait(false); - var insertionPoint = new InsertionPoint(finalDocument, insertionPointAnnotation); + var finalDocument = await document.WithSyntaxRootAsync(finalRoot, cancellationToken).ConfigureAwait(false); + var insertionPoint = new InsertionPoint(finalDocument, insertionPointAnnotation); - return (finalDocument, insertionPoint); - } + return (finalDocument, insertionPoint); + } - private ImmutableArray GetFormattingRules(Document document) - => [GetCustomFormattingRule(document), .. Formatter.GetDefaultFormattingRules(document)]; + private ImmutableArray GetFormattingRules(Document document) + => [GetCustomFormattingRule(document), .. Formatter.GetDefaultFormattingRules(document)]; - private OperationStatus CheckVariableTypes( - OperationStatus status, - AnalyzerResult analyzeResult, - CancellationToken cancellationToken) - { - var semanticModel = OriginalSelectionResult.SemanticDocument.SemanticModel; + private OperationStatus CheckVariableTypes( + OperationStatus status, + AnalyzerResult analyzeResult, + CancellationToken cancellationToken) + { + var semanticModel = OriginalSelectionResult.SemanticDocument.SemanticModel; - // sync selection result to same semantic data as analyzeResult - var firstToken = OriginalSelectionResult.GetFirstTokenInSelection(); - var context = firstToken.Parent; + // sync selection result to same semantic data as analyzeResult + var firstToken = OriginalSelectionResult.GetFirstTokenInSelection(); + var context = firstToken.Parent; - status = TryCheckVariableType(semanticModel, context, analyzeResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken), status); - status = TryCheckVariableType(semanticModel, context, analyzeResult.GetVariablesToSplitOrMoveIntoMethodDefinition(cancellationToken), status); - status = TryCheckVariableType(semanticModel, context, analyzeResult.MethodParameters, status); - status = TryCheckVariableType(semanticModel, context, analyzeResult.GetVariablesToMoveOutToCallSite(cancellationToken), status); - status = TryCheckVariableType(semanticModel, context, analyzeResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken), status); + status = TryCheckVariableType(semanticModel, context, analyzeResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken), status); + status = TryCheckVariableType(semanticModel, context, analyzeResult.GetVariablesToSplitOrMoveIntoMethodDefinition(cancellationToken), status); + status = TryCheckVariableType(semanticModel, context, analyzeResult.MethodParameters, status); + status = TryCheckVariableType(semanticModel, context, analyzeResult.GetVariablesToMoveOutToCallSite(cancellationToken), status); + status = TryCheckVariableType(semanticModel, context, analyzeResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken), status); - if (status.Failed) - return status; + if (status.Failed) + return status; - var checkedStatus = CheckType(semanticModel, context, analyzeResult.ReturnType); - return checkedStatus.With(status); - } + var checkedStatus = CheckType(semanticModel, context, analyzeResult.ReturnType); + return checkedStatus.With(status); + } - private OperationStatus TryCheckVariableType( - SemanticModel semanticModel, - SyntaxNode contextNode, - IEnumerable variables, - OperationStatus status) - { - if (status.Succeeded) + private OperationStatus TryCheckVariableType( + SemanticModel semanticModel, + SyntaxNode contextNode, + IEnumerable variables, + OperationStatus status) { - foreach (var variable in variables) + if (status.Succeeded) { - var originalType = variable.GetVariableType(); - var result = CheckType(semanticModel, contextNode, originalType); - if (result.Failed) - return status.With(result); + foreach (var variable in variables) + { + var originalType = variable.GetVariableType(); + var result = CheckType(semanticModel, contextNode, originalType); + if (result.Failed) + return status.With(result); + } } - } - return status; - } + return status; + } - private OperationStatus CheckType( - SemanticModel semanticModel, SyntaxNode contextNode, ITypeSymbol type) - { - Contract.ThrowIfNull(type); + private OperationStatus CheckType( + SemanticModel semanticModel, SyntaxNode contextNode, ITypeSymbol type) + { + Contract.ThrowIfNull(type); - // this happens when there is no return type - if (type.SpecialType == SpecialType.System_Void) - return OperationStatus.SucceededStatus; + // this happens when there is no return type + if (type.SpecialType == SpecialType.System_Void) + return OperationStatus.SucceededStatus; - if (type.TypeKind is TypeKind.Error or TypeKind.Unknown) - return OperationStatus.ErrorOrUnknownType; + if (type.TypeKind is TypeKind.Error or TypeKind.Unknown) + return OperationStatus.ErrorOrUnknownType; - // if it is type parameter, make sure we are getting same type parameter - foreach (var typeParameter in TypeParameterCollector.Collect(type)) - { - var typeName = ParseTypeName(typeParameter.Name); - var currentType = semanticModel.GetSpeculativeTypeInfo(contextNode.SpanStart, typeName, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; - if (currentType == null || !SymbolEqualityComparer.Default.Equals(currentType, semanticModel.ResolveType(typeParameter))) + // if it is type parameter, make sure we are getting same type parameter + foreach (var typeParameter in TypeParameterCollector.Collect(type)) { - return new OperationStatus(succeeded: true, - string.Format(FeaturesResources.Type_parameter_0_is_hidden_by_another_type_parameter_1, - typeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - currentType == null ? string.Empty : currentType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); + var typeName = ParseTypeName(typeParameter.Name); + var currentType = semanticModel.GetSpeculativeTypeInfo(contextNode.SpanStart, typeName, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; + if (currentType == null || !SymbolEqualityComparer.Default.Equals(currentType, semanticModel.ResolveType(typeParameter))) + { + return new OperationStatus(succeeded: true, + string.Format(FeaturesResources.Type_parameter_0_is_hidden_by_another_type_parameter_1, + typeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + currentType == null ? string.Empty : currentType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); + } } + + return OperationStatus.SucceededStatus; } - return OperationStatus.SucceededStatus; - } + internal static string MakeMethodName(string prefix, string originalName, bool camelCase) + { + var startingWithLetter = originalName.ToCharArray().SkipWhile(c => !char.IsLetter(c)).ToArray(); + var name = startingWithLetter.Length == 0 ? originalName : new string(startingWithLetter); - internal static string MakeMethodName(string prefix, string originalName, bool camelCase) - { - var startingWithLetter = originalName.ToCharArray().SkipWhile(c => !char.IsLetter(c)).ToArray(); - var name = startingWithLetter.Length == 0 ? originalName : new string(startingWithLetter); + if (camelCase && !prefix.IsEmpty()) + { + prefix = char.ToLowerInvariant(prefix[0]) + prefix[1..]; + } - if (camelCase && !prefix.IsEmpty()) - { - prefix = char.ToLowerInvariant(prefix[0]) + prefix[1..]; + return char.IsUpper(name[0]) + ? prefix + name + : prefix + char.ToUpper(name[0]).ToString() + name[1..]; } - - return char.IsUpper(name[0]) - ? prefix + name - : prefix + char.ToUpper(name[0]).ToString() + name[1..]; } } diff --git a/src/Features/Core/Portable/ExtractMethod/ParameterStyle.cs b/src/Features/Core/Portable/ExtractMethod/ParameterStyle.cs index 3e68b33d79676..2437dec0c4e07 100644 --- a/src/Features/Core/Portable/ExtractMethod/ParameterStyle.cs +++ b/src/Features/Core/Portable/ExtractMethod/ParameterStyle.cs @@ -2,43 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.CodeAnalysis.ExtractMethod; -internal sealed class ParameterStyle +internal sealed record class ParameterStyle( + ParameterBehavior ParameterBehavior, + DeclarationBehavior DeclarationBehavior, + DeclarationBehavior SaferDeclarationBehavior) { - public ParameterBehavior ParameterBehavior { get; private set; } - public DeclarationBehavior DeclarationBehavior { get; private set; } - public DeclarationBehavior SaferDeclarationBehavior { get; private set; } - public static readonly ParameterStyle None = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.None, SaferDeclarationBehavior = DeclarationBehavior.None }; + new(ParameterBehavior.None, DeclarationBehavior.None, DeclarationBehavior.None); public static readonly ParameterStyle InputOnly = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.Input, DeclarationBehavior = DeclarationBehavior.None, SaferDeclarationBehavior = DeclarationBehavior.None }; + new(ParameterBehavior.Input, DeclarationBehavior.None, DeclarationBehavior.None); public static readonly ParameterStyle Delete = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.Delete, SaferDeclarationBehavior = DeclarationBehavior.None }; + new(ParameterBehavior.None, DeclarationBehavior.Delete, DeclarationBehavior.None); public static readonly ParameterStyle MoveOut = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.MoveOut, SaferDeclarationBehavior = DeclarationBehavior.SplitOut }; + new(ParameterBehavior.None, DeclarationBehavior.MoveOut, DeclarationBehavior.SplitOut); public static readonly ParameterStyle SplitOut = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.SplitOut, SaferDeclarationBehavior = DeclarationBehavior.SplitOut }; + new(ParameterBehavior.None, DeclarationBehavior.SplitOut, DeclarationBehavior.SplitOut); public static readonly ParameterStyle MoveIn = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.MoveIn, SaferDeclarationBehavior = DeclarationBehavior.SplitIn }; + new(ParameterBehavior.None, DeclarationBehavior.MoveIn, DeclarationBehavior.SplitIn); public static readonly ParameterStyle SplitIn = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.SplitIn, SaferDeclarationBehavior = DeclarationBehavior.SplitIn }; + new(ParameterBehavior.None, DeclarationBehavior.SplitIn, DeclarationBehavior.SplitIn); public static readonly ParameterStyle Out = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.Out, DeclarationBehavior = DeclarationBehavior.None, SaferDeclarationBehavior = DeclarationBehavior.None }; + new(ParameterBehavior.Out, DeclarationBehavior.None, DeclarationBehavior.None); public static readonly ParameterStyle Ref = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.Ref, DeclarationBehavior = DeclarationBehavior.None, SaferDeclarationBehavior = DeclarationBehavior.None }; + new(ParameterBehavior.Ref, DeclarationBehavior.None, DeclarationBehavior.None); public static readonly ParameterStyle OutWithMoveOut = - new ParameterStyle() { ParameterBehavior = ParameterBehavior.Out, DeclarationBehavior = DeclarationBehavior.MoveOut, SaferDeclarationBehavior = DeclarationBehavior.MoveOut }; + new(ParameterBehavior.Out, DeclarationBehavior.MoveOut, DeclarationBehavior.MoveOut); } diff --git a/src/Features/Core/Portable/ExtractMethod/ReturnStyle.cs b/src/Features/Core/Portable/ExtractMethod/ReturnStyle.cs index 03e4097d93042..e0c584b41d39f 100644 --- a/src/Features/Core/Portable/ExtractMethod/ReturnStyle.cs +++ b/src/Features/Core/Portable/ExtractMethod/ReturnStyle.cs @@ -2,25 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.CodeAnalysis.ExtractMethod; -internal sealed class ReturnStyle +internal sealed record class ReturnStyle( + ParameterBehavior ParameterBehavior, + ReturnBehavior ReturnBehavior, + DeclarationBehavior DeclarationBehavior) { - public ParameterBehavior ParameterBehavior { get; private set; } - public ReturnBehavior ReturnBehavior { get; private set; } - public DeclarationBehavior DeclarationBehavior { get; private set; } - public static readonly ReturnStyle None = - new ReturnStyle() { ParameterBehavior = ParameterBehavior.None, ReturnBehavior = ReturnBehavior.None, DeclarationBehavior = DeclarationBehavior.None }; + new(ParameterBehavior.None, ReturnBehavior.None, DeclarationBehavior.None); public static readonly ReturnStyle AssignmentWithInput = - new ReturnStyle() { ParameterBehavior = ParameterBehavior.Input, ReturnBehavior = ReturnBehavior.Assignment, DeclarationBehavior = DeclarationBehavior.None }; + new(ParameterBehavior.Input, ReturnBehavior.Assignment, DeclarationBehavior.None); public static readonly ReturnStyle AssignmentWithNoInput = - new ReturnStyle() { ParameterBehavior = ParameterBehavior.None, ReturnBehavior = ReturnBehavior.Assignment, DeclarationBehavior = DeclarationBehavior.SplitIn }; + new(ParameterBehavior.None, ReturnBehavior.Assignment, DeclarationBehavior.SplitIn); public static readonly ReturnStyle Initialization = - new ReturnStyle() { ParameterBehavior = ParameterBehavior.None, ReturnBehavior = ReturnBehavior.Initialization, DeclarationBehavior = DeclarationBehavior.SplitOut }; + new(ParameterBehavior.None, ReturnBehavior.Initialization, DeclarationBehavior.SplitOut); } diff --git a/src/Features/Core/Portable/ExtractMethod/SelectionInfo.cs b/src/Features/Core/Portable/ExtractMethod/SelectionInfo.cs new file mode 100644 index 0000000000000..1abfe670d23ba --- /dev/null +++ b/src/Features/Core/Portable/ExtractMethod/SelectionInfo.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExtractMethod; + +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> +{ + internal sealed record SelectionInfo + { + public OperationStatus Status { get; init; } + + public TextSpan OriginalSpan { get; init; } + public TextSpan FinalSpan { get; init; } + + public SyntaxNode CommonRootFromOriginalSpan { get; init; } + + public SyntaxToken FirstTokenInOriginalSpan { get; init; } + public SyntaxToken LastTokenInOriginalSpan { get; init; } + + public SyntaxToken FirstTokenInFinalSpan { get; init; } + public SyntaxToken LastTokenInFinalSpan { get; init; } + + public bool SelectionInExpression { get; init; } + public bool SelectionInSingleStatement { get; init; } + + /// + /// For VB. C# should just use standard with operator. + /// + public SelectionInfo With( + Optional status = default, + Optional finalSpan = default, + Optional firstTokenInFinalSpan = default, + Optional lastTokenInFinalSpan = default, + Optional selectionInExpression = default, + Optional selectionInSingleStatement = default) + { + var resultStatus = status.HasValue ? status.Value : this.Status; + var resultFinalSpan = finalSpan.HasValue ? finalSpan.Value : this.FinalSpan; + var resultFirstTokenInFinalSpan = firstTokenInFinalSpan.HasValue ? firstTokenInFinalSpan.Value : this.FirstTokenInFinalSpan; + var resultLastTokenInFinalSpan = lastTokenInFinalSpan.HasValue ? lastTokenInFinalSpan.Value : this.LastTokenInFinalSpan; + var resultSelectionInExpression = selectionInExpression.HasValue ? selectionInExpression.Value : this.SelectionInExpression; + var resultSelectionInSingleStatement = selectionInSingleStatement.HasValue ? selectionInSingleStatement.Value : this.SelectionInSingleStatement; + + return this with + { + Status = resultStatus, + FinalSpan = resultFinalSpan, + FirstTokenInFinalSpan = resultFirstTokenInFinalSpan, + LastTokenInFinalSpan = resultLastTokenInFinalSpan, + SelectionInExpression = resultSelectionInExpression, + SelectionInSingleStatement = resultSelectionInSingleStatement, + }; + } + + public SelectionType GetSelectionType() + { + if (this.SelectionInExpression) + return SelectionType.Expression; + + var firstStatement = this.FirstTokenInFinalSpan.GetRequiredAncestor(); + var lastStatement = this.LastTokenInFinalSpan.GetRequiredAncestor(); + if (firstStatement == lastStatement || firstStatement.Span.Contains(lastStatement.Span)) + return SelectionType.SingleStatement; + + return SelectionType.MultipleStatements; + } + + public TextSpan GetControlFlowSpan() + => TextSpan.FromBounds(this.FirstTokenInFinalSpan.SpanStart, this.LastTokenInFinalSpan.Span.End); + } +} diff --git a/src/Features/Core/Portable/ExtractMethod/SelectionResult.cs b/src/Features/Core/Portable/ExtractMethod/SelectionResult.cs index a2e9da4368fbd..454365fd0453d 100644 --- a/src/Features/Core/Portable/ExtractMethod/SelectionResult.cs +++ b/src/Features/Core/Portable/ExtractMethod/SelectionResult.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Linq; using System.Threading; @@ -15,213 +13,218 @@ namespace Microsoft.CodeAnalysis.ExtractMethod; -/// -/// clean up this code when we do selection validator work. -/// -internal abstract class SelectionResult( - TextSpan originalSpan, - TextSpan finalSpan, - bool selectionInExpression, - SemanticDocument document, - SyntaxAnnotation firstTokenAnnotation, - SyntaxAnnotation lastTokenAnnotation, - bool selectionChanged) - where TStatementSyntax : SyntaxNode +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, + TSelectionResult, + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> { - private bool? _createAsyncMethod; - - public TextSpan OriginalSpan { get; } = originalSpan; - public TextSpan FinalSpan { get; } = finalSpan; - public bool SelectionInExpression { get; } = selectionInExpression; - public SemanticDocument SemanticDocument { get; private set; } = document; - public SyntaxAnnotation FirstTokenAnnotation { get; } = firstTokenAnnotation; - public SyntaxAnnotation LastTokenAnnotation { get; } = lastTokenAnnotation; - public bool SelectionChanged { get; } = selectionChanged; + internal abstract class SelectionResult( + SemanticDocument document, + SelectionType selectionType, + TextSpan originalSpan, + TextSpan finalSpan, + bool selectionChanged) + { + protected static readonly SyntaxAnnotation s_firstTokenAnnotation = new(); + protected static readonly SyntaxAnnotation s_lastTokenAnnotation = new(); - protected abstract ISyntaxFacts SyntaxFacts { get; } - protected abstract bool UnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken); + private bool? _createAsyncMethod; - public abstract TStatementSyntax GetFirstStatementUnderContainer(); - public abstract TStatementSyntax GetLastStatementUnderContainer(); + public SemanticDocument SemanticDocument { get; private set; } = document; + public TextSpan OriginalSpan { get; } = originalSpan; + public TextSpan FinalSpan { get; } = finalSpan; + public SelectionType SelectionType { get; } = selectionType; + public bool SelectionChanged { get; } = selectionChanged; - public abstract bool ContainingScopeHasAsyncKeyword(); + protected abstract ISyntaxFacts SyntaxFacts { get; } + protected abstract bool UnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken); - public abstract SyntaxNode GetContainingScope(); - public abstract SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken); + public abstract TExecutableStatementSyntax GetFirstStatementUnderContainer(); + public abstract TExecutableStatementSyntax GetLastStatementUnderContainer(); - public abstract (ITypeSymbol returnType, bool returnsByRef) GetReturnType(); + public abstract bool ContainingScopeHasAsyncKeyword(); - public ITypeSymbol GetContainingScopeType() - { - var (typeSymbol, _) = GetReturnType(); - return typeSymbol; - } + public abstract SyntaxNode GetContainingScope(); + public abstract SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken); - public virtual SyntaxNode GetNodeForDataFlowAnalysis() => GetContainingScope(); + public abstract (ITypeSymbol? returnType, bool returnsByRef) GetReturnType(); - public SelectionResult With(SemanticDocument document) - { - if (SemanticDocument == document) + public ITypeSymbol? GetContainingScopeType() { - return this; + var (typeSymbol, _) = GetReturnType(); + return typeSymbol; } - var clone = (SelectionResult)MemberwiseClone(); - clone.SemanticDocument = document; + public bool IsExtractMethodOnExpression => this.SelectionType == SelectionType.Expression; + public bool IsExtractMethodOnSingleStatement => this.SelectionType == SelectionType.SingleStatement; + public bool IsExtractMethodOnMultipleStatements => this.SelectionType == SelectionType.MultipleStatements; - return clone; - } + public virtual SyntaxNode GetNodeForDataFlowAnalysis() => GetContainingScope(); - public SyntaxToken GetFirstTokenInSelection() - => SemanticDocument.GetTokenWithAnnotation(FirstTokenAnnotation); + public SelectionResult With(SemanticDocument document) + { + if (SemanticDocument == document) + { + return this; + } - public SyntaxToken GetLastTokenInSelection() - => SemanticDocument.GetTokenWithAnnotation(LastTokenAnnotation); + var clone = (SelectionResult)MemberwiseClone(); + clone.SemanticDocument = document; - public TNode GetContainingScopeOf() where TNode : SyntaxNode - { - var containingScope = GetContainingScope(); - return containingScope.GetAncestorOrThis(); - } + return clone; + } - public bool IsExtractMethodOnSingleStatement() - { - var firstStatement = this.GetFirstStatement(); - var lastStatement = this.GetLastStatement(); + public SyntaxToken GetFirstTokenInSelection() + => SemanticDocument.GetTokenWithAnnotation(s_firstTokenAnnotation); - return firstStatement == lastStatement || firstStatement.Span.Contains(lastStatement.Span); - } + public SyntaxToken GetLastTokenInSelection() + => SemanticDocument.GetTokenWithAnnotation(s_lastTokenAnnotation); - public bool IsExtractMethodOnMultipleStatements() - { - var first = this.GetFirstStatement(); - var last = this.GetLastStatement(); + public TNode? GetContainingScopeOf() where TNode : SyntaxNode + { + var containingScope = GetContainingScope(); + return containingScope.GetAncestorOrThis(); + } - if (first != last) + public TExecutableStatementSyntax GetFirstStatement() { - var firstUnderContainer = this.GetFirstStatementUnderContainer(); - var lastUnderContainer = this.GetLastStatementUnderContainer(); - Contract.ThrowIfFalse(this.SyntaxFacts.AreStatementsInSameContainer(firstUnderContainer, lastUnderContainer)); - return true; + Contract.ThrowIfTrue(IsExtractMethodOnExpression); + + var token = GetFirstTokenInSelection(); + return token.GetRequiredAncestor(); } - return false; - } + public TExecutableStatementSyntax GetLastStatement() + { + Contract.ThrowIfTrue(IsExtractMethodOnExpression); - public TStatementSyntax GetFirstStatement() - { - Contract.ThrowIfTrue(SelectionInExpression); + var token = GetLastTokenInSelection(); + return token.GetRequiredAncestor(); + } - var token = GetFirstTokenInSelection(); - return token.GetAncestor(); - } + public bool CreateAsyncMethod() + { + _createAsyncMethod ??= CreateAsyncMethodWorker(); + return _createAsyncMethod.Value; - public TStatementSyntax GetLastStatement() - { - Contract.ThrowIfTrue(SelectionInExpression); + bool CreateAsyncMethodWorker() + { + var firstToken = GetFirstTokenInSelection(); + var lastToken = GetLastTokenInSelection(); + var syntaxFacts = SemanticDocument.GetRequiredLanguageService(); - var token = GetLastTokenInSelection(); - return token.GetAncestor(); - } + for (var currentToken = firstToken; + currentToken.Span.End < lastToken.SpanStart; + currentToken = currentToken.GetNextToken()) + { + // [| + // async () => await .... + // |] + // + // for the case above, even if the selection contains "await", it doesn't belong to the enclosing block + // which extract method is applied to + if (syntaxFacts.IsAwaitKeyword(currentToken) + && !UnderAnonymousOrLocalMethod(currentToken, firstToken, lastToken)) + { + return true; + } + } - public bool CreateAsyncMethod() - { - _createAsyncMethod ??= CreateAsyncMethodWorker(); - return _createAsyncMethod.Value; + return false; + } + } - bool CreateAsyncMethodWorker() + public bool ShouldCallConfigureAwaitFalse() { + var syntaxFacts = SemanticDocument.GetRequiredLanguageService(); + var firstToken = GetFirstTokenInSelection(); var lastToken = GetLastTokenInSelection(); - var syntaxFacts = SemanticDocument.Project.Services.GetService(); - for (var currentToken = firstToken; - currentToken.Span.End < lastToken.SpanStart; - currentToken = currentToken.GetNextToken()) + var span = TextSpan.FromBounds(firstToken.SpanStart, lastToken.Span.End); + + foreach (var node in SemanticDocument.Root.DescendantNodesAndSelf()) { - // [| - // async () => await .... - // |] - // - // for the case above, even if the selection contains "await", it doesn't belong to the enclosing block - // which extract method is applied to - if (syntaxFacts.IsAwaitKeyword(currentToken) - && !UnderAnonymousOrLocalMethod(currentToken, firstToken, lastToken)) - { + if (!node.Span.OverlapsWith(span)) + continue; + + if (IsConfigureAwaitFalse(node) && !UnderAnonymousOrLocalMethod(node.GetFirstToken(), firstToken, lastToken)) return true; - } } return false; - } - } - public bool ShouldCallConfigureAwaitFalse() - { - var syntaxFacts = SemanticDocument.Project.Services.GetService(); + bool IsConfigureAwaitFalse(SyntaxNode node) + { + if (!syntaxFacts.IsInvocationExpression(node)) + return false; - var firstToken = GetFirstTokenInSelection(); - var lastToken = GetLastTokenInSelection(); + var invokedExpression = syntaxFacts.GetExpressionOfInvocationExpression(node); + if (!syntaxFacts.IsSimpleMemberAccessExpression(invokedExpression)) + return false; - var span = TextSpan.FromBounds(firstToken.SpanStart, lastToken.Span.End); + var name = syntaxFacts.GetNameOfMemberAccessExpression(invokedExpression); + var identifier = syntaxFacts.GetIdentifierOfSimpleName(name); + if (!syntaxFacts.StringComparer.Equals(identifier.ValueText, nameof(Task.ConfigureAwait))) + return false; - foreach (var node in SemanticDocument.Root.DescendantNodesAndSelf()) - { - if (!node.Span.OverlapsWith(span)) - continue; + var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(node); + if (arguments.Count != 1) + return false; - if (IsConfigureAwaitFalse(node) && !UnderAnonymousOrLocalMethod(node.GetFirstToken(), firstToken, lastToken)) - return true; + var expression = syntaxFacts.GetExpressionOfArgument(arguments[0]); + return syntaxFacts.IsFalseLiteralExpression(expression); + } } - return false; - - bool IsConfigureAwaitFalse(SyntaxNode node) + /// f + /// convert text span to node range for the flow analysis API + /// + public (TExecutableStatementSyntax firstStatement, TExecutableStatementSyntax lastStatement) GetFlowAnalysisNodeRange() { - if (!syntaxFacts.IsInvocationExpression(node)) - return false; + var first = this.GetFirstStatement(); + var last = this.GetLastStatement(); - var invokedExpression = syntaxFacts.GetExpressionOfInvocationExpression(node); - if (!syntaxFacts.IsSimpleMemberAccessExpression(invokedExpression)) - return false; - - var name = syntaxFacts.GetNameOfMemberAccessExpression(invokedExpression); - var identifier = syntaxFacts.GetIdentifierOfSimpleName(name); - if (!syntaxFacts.StringComparer.Equals(identifier.ValueText, nameof(Task.ConfigureAwait))) - return false; - - var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(node); - if (arguments.Count != 1) - return false; + // single statement case + if (first == last || + first.Span.Contains(last.Span)) + { + return (first, first); + } - var expression = syntaxFacts.GetExpressionOfArgument(arguments[0]); - return syntaxFacts.IsFalseLiteralExpression(expression); + // multiple statement case + var firstUnderContainer = this.GetFirstStatementUnderContainer(); + var lastUnderContainer = this.GetLastStatementUnderContainer(); + return (firstUnderContainer, lastUnderContainer); } - } - /// - /// create a new root node from the given root after adding annotations to the tokens - /// - /// tokens should belong to the given root - /// - protected static SyntaxNode AddAnnotations(SyntaxNode root, IEnumerable<(SyntaxToken, SyntaxAnnotation)> pairs) - { - Contract.ThrowIfNull(root); + /// + /// create a new root node from the given root after adding annotations to the tokens + /// + /// tokens should belong to the given root + /// + protected static SyntaxNode AddAnnotations(SyntaxNode root, IEnumerable<(SyntaxToken, SyntaxAnnotation)> pairs) + { + Contract.ThrowIfNull(root); - var tokenMap = pairs.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); - return root.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o])); - } + var tokenMap = pairs.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); + return root.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o])); + } - /// - /// create a new root node from the given root after adding annotations to the nodes - /// - /// nodes should belong to the given root - /// - protected static SyntaxNode AddAnnotations(SyntaxNode root, IEnumerable<(SyntaxNode, SyntaxAnnotation)> pairs) - { - Contract.ThrowIfNull(root); + /// + /// create a new root node from the given root after adding annotations to the nodes + /// + /// nodes should belong to the given root + /// + protected static SyntaxNode AddAnnotations(SyntaxNode root, IEnumerable<(SyntaxNode, SyntaxAnnotation)> pairs) + { + Contract.ThrowIfNull(root); - var tokenMap = pairs.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); - return root.ReplaceNodes(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o])); + var tokenMap = pairs.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); + return root.ReplaceNodes(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o])); + } } } diff --git a/src/Features/Core/Portable/ExtractMethod/SelectionType.cs b/src/Features/Core/Portable/ExtractMethod/SelectionType.cs new file mode 100644 index 0000000000000..eaf3eb3619c6c --- /dev/null +++ b/src/Features/Core/Portable/ExtractMethod/SelectionType.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.ExtractMethod; + +internal enum SelectionType +{ + Expression, + SingleStatement, + MultipleStatements, +} diff --git a/src/Features/Core/Portable/ExtractMethod/SelectionValidator.cs b/src/Features/Core/Portable/ExtractMethod/SelectionValidator.cs index 9b78f4bc5cb5d..0812754e629af 100644 --- a/src/Features/Core/Portable/ExtractMethod/SelectionValidator.cs +++ b/src/Features/Core/Portable/ExtractMethod/SelectionValidator.cs @@ -4,208 +4,202 @@ #nullable disable -using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExtractMethod; -internal abstract partial class SelectionValidator< +internal abstract partial class AbstractExtractMethodService< + TValidator, + TExtractor, TSelectionResult, - TStatementSyntax>( + TStatementSyntax, + TExecutableStatementSyntax, + TExpressionSyntax> +{ + public abstract partial class SelectionValidator( SemanticDocument document, TextSpan textSpan) - where TSelectionResult : SelectionResult - where TStatementSyntax : SyntaxNode -{ - protected readonly SemanticDocument SemanticDocument = document; - protected readonly TextSpan OriginalSpan = textSpan; + { + protected readonly SemanticDocument SemanticDocument = document; + protected readonly TextSpan OriginalSpan = textSpan; - public bool ContainsValidSelection => !OriginalSpan.IsEmpty; + public bool ContainsValidSelection => !OriginalSpan.IsEmpty; - public abstract Task<(TSelectionResult, OperationStatus)> GetValidSelectionAsync(CancellationToken cancellationToken); - public abstract IEnumerable GetOuterReturnStatements(SyntaxNode commonRoot, IEnumerable jumpsOutOfRegion); - public abstract bool IsFinalSpanSemanticallyValidSpan(SyntaxNode node, TextSpan textSpan, IEnumerable returnStatements, CancellationToken cancellationToken); - public abstract bool ContainsNonReturnExitPointsStatements(IEnumerable jumpsOutOfRegion); + protected abstract SelectionInfo GetInitialSelectionInfo(CancellationToken cancellationToken); + protected abstract Task CreateSelectionResultAsync(SelectionInfo selectionInfo, CancellationToken cancellationToken); - protected bool IsFinalSpanSemanticallyValidSpan( - SemanticModel semanticModel, TextSpan textSpan, (SyntaxNode, SyntaxNode) range, CancellationToken cancellationToken) - { - var controlFlowAnalysisData = semanticModel.AnalyzeControlFlow(range.Item1, range.Item2); + public abstract ImmutableArray GetOuterReturnStatements(SyntaxNode commonRoot, ImmutableArray jumpsOutOfRegion); + public abstract bool IsFinalSpanSemanticallyValidSpan(TextSpan textSpan, ImmutableArray returnStatements, CancellationToken cancellationToken); + public abstract bool ContainsNonReturnExitPointsStatements(ImmutableArray jumpsOutOfRegion); - // there must be no control in and out of given span - if (controlFlowAnalysisData.EntryPoints.Any()) - { - return false; - } + protected abstract bool AreStatementsInSameContainer(TStatementSyntax statement1, TStatementSyntax statement2); - // check something like continue, break, yield break, yield return, and etc - if (ContainsNonReturnExitPointsStatements(controlFlowAnalysisData.ExitPoints)) + public async Task<(TSelectionResult, OperationStatus)> GetValidSelectionAsync(CancellationToken cancellationToken) { - return false; - } + if (!this.ContainsValidSelection) + return (null, OperationStatus.FailedWithUnknownReason); - // okay, there is no branch out, check whether next statement can be executed normally - var returnStatements = GetOuterReturnStatements(range.Item1.GetCommonRoot(range.Item2), controlFlowAnalysisData.ExitPoints); - if (!returnStatements.Any()) - { - if (!controlFlowAnalysisData.EndPointIsReachable) + var selectionInfo = GetInitialSelectionInfo(cancellationToken); + if (selectionInfo.Status.Failed) + return (null, selectionInfo.Status); + + if (!selectionInfo.SelectionInExpression) { - // REVIEW: should we just do extract method regardless or show some warning to user? - // in dev10, looks like we went ahead and did the extract method even if selection contains - // unreachable code. + var root = SemanticDocument.Root; + + var controlFlowSpan = selectionInfo.GetControlFlowSpan(); + var statementRange = GetStatementRangeContainedInSpan(root, controlFlowSpan, cancellationToken); + if (statementRange == null) + return (null, selectionInfo.Status.With(succeeded: false, FeaturesResources.Cannot_determine_valid_range_of_statements_to_extract)); + + var isFinalSpanSemanticallyValid = IsFinalSpanSemanticallyValidSpan(controlFlowSpan, statementRange.Value, cancellationToken); + if (!isFinalSpanSemanticallyValid) + { + selectionInfo = selectionInfo with + { + Status = selectionInfo.Status.With(succeeded: true, FeaturesResources.Not_all_code_paths_return), + }; + } } - return true; - } - - // okay, only branch was return. make sure we have all return in the selection. - - // check for special case, if end point is not reachable, we don't care the selection - // actually contains all return statements. we just let extract method go through - // and work like we did in dev10 - if (!controlFlowAnalysisData.EndPointIsReachable) - { - return true; + var selectionResult = await CreateSelectionResultAsync(selectionInfo, cancellationToken).ConfigureAwait(false); + return (selectionResult, selectionInfo.Status); } - // there is a return statement, and current position is reachable. let's check whether this is a case where that is okay - return IsFinalSpanSemanticallyValidSpan(semanticModel.SyntaxTree.GetRoot(cancellationToken), textSpan, returnStatements, cancellationToken); - } - - protected static (T, T)? GetStatementRangeContainingSpan( - ISyntaxFacts syntaxFacts, - SyntaxNode root, TextSpan textSpan, CancellationToken cancellationToken) where T : SyntaxNode - { - // use top-down approach to find smallest statement range that contains given span. - // this approach is more expansive than bottom-up approach I used before but way simpler and easy to understand - var token1 = root.FindToken(textSpan.Start); - var token2 = root.FindTokenFromEnd(textSpan.End); - - var commonRoot = token1.GetCommonRoot(token2).GetAncestorOrThis() ?? root; - - var firstStatement = (T)null; - var lastStatement = (T)null; - - var spine = new List(); - - foreach (var stmt in commonRoot.DescendantNodesAndSelf().OfType()) + protected bool IsFinalSpanSemanticallyValidSpan( + TextSpan textSpan, (SyntaxNode, SyntaxNode) range, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + var controlFlowAnalysisData = this.SemanticDocument.SemanticModel.AnalyzeControlFlow(range.Item1, range.Item2); - // quick skip check. - // - not containing at all - if (stmt.Span.End < textSpan.Start) + // there must be no control in and out of given span + if (controlFlowAnalysisData.EntryPoints.Any()) { - continue; + return false; } - // quick exit check - // - passed candidate statements - if (textSpan.End < stmt.SpanStart) + // check something like continue, break, yield break, yield return, and etc + if (ContainsNonReturnExitPointsStatements(controlFlowAnalysisData.ExitPoints)) { - break; + return false; } - if (stmt.SpanStart <= textSpan.Start) + // okay, there is no branch out, check whether next statement can be executed normally + var returnStatements = GetOuterReturnStatements(range.Item1.GetCommonRoot(range.Item2), controlFlowAnalysisData.ExitPoints); + if (!returnStatements.Any()) { - // keep track spine - spine.Add(stmt); + if (!controlFlowAnalysisData.EndPointIsReachable) + { + // REVIEW: should we just do extract method regardless or show some warning to user? + // in dev10, looks like we went ahead and did the extract method even if selection contains + // unreachable code. + } + + return true; } - if (textSpan.End <= stmt.Span.End && spine.Any(s => CanMergeExistingSpineWithCurrent(syntaxFacts, s, stmt))) - { - // malformed code or selection can make spine to have more than an elements - firstStatement = spine.First(s => CanMergeExistingSpineWithCurrent(syntaxFacts, s, stmt)); - lastStatement = stmt; + // okay, only branch was return. make sure we have all return in the selection. - spine.Clear(); + // check for special case, if end point is not reachable, we don't care the selection + // actually contains all return statements. we just let extract method go through + // and work like we did in dev10 + if (!controlFlowAnalysisData.EndPointIsReachable) + { + return true; } - } - if (firstStatement == null || lastStatement == null) - { - return null; + // there is a return statement, and current position is reachable. let's check whether this is a case where that is okay + return IsFinalSpanSemanticallyValidSpan(textSpan, returnStatements, cancellationToken); } - return (firstStatement, lastStatement); - - static bool CanMergeExistingSpineWithCurrent(ISyntaxFacts syntaxFacts, T existing, T current) - => syntaxFacts.AreStatementsInSameContainer(existing, current); - } + protected (TStatementSyntax firstStatement, TStatementSyntax lastStatement)? GetStatementRangeContainingSpan( + SyntaxNode root, + TextSpan textSpan, + CancellationToken cancellationToken) + { + // use top-down approach to find smallest statement range that contains given span. + // this approach is more expansive than bottom-up approach I used before but way simpler and easy to understand + var token1 = root.FindToken(textSpan.Start); + var token2 = root.FindTokenFromEnd(textSpan.End); - protected static (T, T)? GetStatementRangeContainedInSpan( - SyntaxNode root, TextSpan textSpan, CancellationToken cancellationToken) where T : SyntaxNode - { - // use top-down approach to find largest statement range contained in the given span - // this method is a bit more expensive than bottom-up approach, but way more simpler than the other approach. - var token1 = root.FindToken(textSpan.Start); - var token2 = root.FindTokenFromEnd(textSpan.End); + var commonRoot = token1.GetCommonRoot(token2).GetAncestorOrThis() ?? root; - var commonRoot = token1.GetCommonRoot(token2).GetAncestorOrThis() ?? root; + TStatementSyntax firstStatement = null; + TStatementSyntax lastStatement = null; - T firstStatement = null; - T lastStatement = null; + var spine = new List(); - foreach (var stmt in commonRoot.DescendantNodesAndSelf().OfType()) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (firstStatement == null && stmt.SpanStart >= textSpan.Start) + foreach (var statement in commonRoot.DescendantNodesAndSelf().OfType()) { - firstStatement = stmt; + cancellationToken.ThrowIfCancellationRequested(); + + // quick skip check. + // - not containing at all + if (statement.Span.End < textSpan.Start) + continue; + + // quick exit check + // - passed candidate statements + if (textSpan.End < statement.SpanStart) + break; + + if (statement.SpanStart <= textSpan.Start) + { + // keep track spine + spine.Add(statement); + } + + if (textSpan.End <= statement.Span.End && spine.Any(s => AreStatementsInSameContainer(s, statement))) + { + // malformed code or selection can make spine to have more than an elements + firstStatement = spine.First(s => AreStatementsInSameContainer(s, statement)); + lastStatement = statement; + + spine.Clear(); + } } - if (firstStatement != null && stmt.Span.End <= textSpan.End && stmt.Parent == firstStatement.Parent) - { - lastStatement = stmt; - } - } + if (firstStatement == null || lastStatement == null) + return null; - if (firstStatement == null || lastStatement == null) - { - return null; + return (firstStatement, lastStatement); } - return (firstStatement, lastStatement); - } - - protected sealed class SelectionInfo - { - public OperationStatus Status { get; set; } + protected static (TStatementSyntax firstStatement, TStatementSyntax)? GetStatementRangeContainedInSpan( + SyntaxNode root, TextSpan textSpan, CancellationToken cancellationToken) + { + // use top-down approach to find largest statement range contained in the given span + // this method is a bit more expensive than bottom-up approach, but way more simpler than the other approach. + var token1 = root.FindToken(textSpan.Start); + var token2 = root.FindTokenFromEnd(textSpan.End); - public TextSpan OriginalSpan { get; set; } - public TextSpan FinalSpan { get; set; } + var commonRoot = token1.GetCommonRoot(token2).GetAncestorOrThis() ?? root; - public SyntaxNode CommonRootFromOriginalSpan { get; set; } + TStatementSyntax firstStatement = null; + TStatementSyntax lastStatement = null; - public SyntaxToken FirstTokenInOriginalSpan { get; set; } - public SyntaxToken LastTokenInOriginalSpan { get; set; } + foreach (var statement in commonRoot.DescendantNodesAndSelf().OfType()) + { + cancellationToken.ThrowIfCancellationRequested(); - public SyntaxToken FirstTokenInFinalSpan { get; set; } - public SyntaxToken LastTokenInFinalSpan { get; set; } + if (firstStatement == null && statement.SpanStart >= textSpan.Start) + firstStatement = statement; - public bool SelectionInExpression { get; set; } - public bool SelectionInSingleStatement { get; set; } + if (firstStatement != null && statement.Span.End <= textSpan.End && statement.Parent == firstStatement.Parent) + lastStatement = statement; + } - public SelectionInfo WithStatus(Func statusGetter) - => With(s => s.Status = statusGetter(s.Status)); + if (firstStatement == null || lastStatement == null) + return null; - public SelectionInfo With(Action valueSetter) - { - var newInfo = Clone(); - valueSetter(newInfo); - return newInfo; + return (firstStatement, lastStatement); } - - public SelectionInfo Clone() - => (SelectionInfo)MemberwiseClone(); } } diff --git a/src/Features/Core/Portable/ExtractMethod/VariableStyle.cs b/src/Features/Core/Portable/ExtractMethod/VariableStyle.cs index 636c1a883a6bf..379e1db6d817a 100644 --- a/src/Features/Core/Portable/ExtractMethod/VariableStyle.cs +++ b/src/Features/Core/Portable/ExtractMethod/VariableStyle.cs @@ -2,51 +2,48 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.CodeAnalysis.ExtractMethod; -internal sealed class VariableStyle +internal sealed record class VariableStyle( + ParameterStyle ParameterStyle, + ReturnStyle ReturnStyle) { - public ParameterStyle ParameterStyle { get; private set; } - public ReturnStyle ReturnStyle { get; private set; } - public static readonly VariableStyle None = - new VariableStyle() { ParameterStyle = ParameterStyle.None, ReturnStyle = ReturnStyle.None }; + new(ParameterStyle.None, ReturnStyle.None); public static readonly VariableStyle InputOnly = - new VariableStyle() { ParameterStyle = ParameterStyle.InputOnly, ReturnStyle = ReturnStyle.None }; + new(ParameterStyle.InputOnly, ReturnStyle.None); public static readonly VariableStyle Delete = - new VariableStyle() { ParameterStyle = ParameterStyle.Delete, ReturnStyle = ReturnStyle.None }; + new(ParameterStyle.Delete, ReturnStyle.None); public static readonly VariableStyle MoveOut = - new VariableStyle() { ParameterStyle = ParameterStyle.MoveOut, ReturnStyle = ReturnStyle.None }; + new(ParameterStyle.MoveOut, ReturnStyle.None); public static readonly VariableStyle SplitOut = - new VariableStyle() { ParameterStyle = ParameterStyle.SplitOut, ReturnStyle = ReturnStyle.None }; + new(ParameterStyle.SplitOut, ReturnStyle.None); public static readonly VariableStyle MoveIn = - new VariableStyle() { ParameterStyle = ParameterStyle.MoveIn, ReturnStyle = ReturnStyle.None }; + new(ParameterStyle.MoveIn, ReturnStyle.None); public static readonly VariableStyle SplitIn = - new VariableStyle() { ParameterStyle = ParameterStyle.SplitIn, ReturnStyle = ReturnStyle.None }; + new(ParameterStyle.SplitIn, ReturnStyle.None); public static readonly VariableStyle NotUsed = - new VariableStyle() { ParameterStyle = ParameterStyle.MoveOut, ReturnStyle = ReturnStyle.Initialization }; + new(ParameterStyle.MoveOut, ReturnStyle.Initialization); public static readonly VariableStyle Ref = - new VariableStyle() { ParameterStyle = ParameterStyle.Ref, ReturnStyle = ReturnStyle.AssignmentWithInput }; + new(ParameterStyle.Ref, ReturnStyle.AssignmentWithInput); public static readonly VariableStyle OnlyAsRefParam = - new VariableStyle() { ParameterStyle = ParameterStyle.Ref, ReturnStyle = ReturnStyle.None }; + new(ParameterStyle.Ref, ReturnStyle.None); public static readonly VariableStyle Out = - new VariableStyle() { ParameterStyle = ParameterStyle.Out, ReturnStyle = ReturnStyle.AssignmentWithNoInput }; + new(ParameterStyle.Out, ReturnStyle.AssignmentWithNoInput); public static readonly VariableStyle OutWithErrorInput = - new VariableStyle() { ParameterStyle = ParameterStyle.Out, ReturnStyle = ReturnStyle.AssignmentWithInput }; + new(ParameterStyle.Out, ReturnStyle.AssignmentWithInput); public static readonly VariableStyle OutWithMoveOut = - new VariableStyle() { ParameterStyle = ParameterStyle.OutWithMoveOut, ReturnStyle = ReturnStyle.Initialization }; + new(ParameterStyle.OutWithMoveOut, ReturnStyle.Initialization); } diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index f08b468439448..4b3c92533f5df 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -3195,4 +3195,10 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Unable to create '{0}' + + Cannot determine valid range of statements to extract + + + No valid statement range to extract + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 0e1e787f93424..85a7397f6a55c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -335,6 +335,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Změny se nedají použít – neočekávaná chyba: {0} + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range Do rozsahu znaků nejde zahrnout třídu \{0}. @@ -1260,6 +1265,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Žádný platný výběr k provedení extrakce + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 2092d6fc272db..7f204e2265b53 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -335,6 +335,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Änderungen können nicht angewendet werden -- unerwarteter Fehler: "{0}" + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range Die Klasse \{0} kann nicht in den Zeichenbereich aufgenommen werden. @@ -1260,6 +1265,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Keine gültige Auswahl zum Durchführen der Extraktion. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 5b0c040c00da1..26d3746dc168b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -335,6 +335,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa No se pueden aplicar los cambios. Error inesperado: "{0}" + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range No se incluye la clase \{0} en el intervalo de caracteres @@ -1260,6 +1265,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Selección no válida para efectuar extracción. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 3f0cebcea3dd8..95e8055573e62 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -335,6 +335,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Impossible d'appliquer les changements -- Erreur inattendue : '{0}' + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range Impossible d'inclure la classe \{0} dans la plage de caractères @@ -1260,6 +1265,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Pas de sélection valide pour procéder à l'extraction. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index e2b79c2f56e0c..e800c530aeec8 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -335,6 +335,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Non è possibile applicare le modifiche. Errore imprevisto: '{0}' + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range Non è possibile includere la classe \{0} nell'intervallo di caratteri @@ -1260,6 +1265,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa La selezione non è valida per eseguire l'estrazione. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index aa269dbe29477..a7894ceb4119a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -335,6 +335,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 変更を適用できません。予期しないエラー: '{0}' + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range 文字範囲にクラス \{0} を含めることはできません @@ -1260,6 +1265,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 抽出を行うための有効な選択がありません。 + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 6072e9296ef48..7d7b7f3698995 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -335,6 +335,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 변경 내용을 적용할 수 없음 -- 예기치 않은 오류: '{0}' + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range 문자 범위에 \{0} 클래스를 포함할 수 없습니다. @@ -1260,6 +1265,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 추출을 수행할 선택 항목이 잘못되었습니다. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 47c25432b254b..7867a63c08500 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -335,6 +335,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Nie można zastosować zmian — nieoczekiwany błąd: „{0}” + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range Nie można uwzględnić klasy \{0} w zakresie znaków @@ -1260,6 +1265,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Brak prawidłowego zaznaczenia do wyodrębnienia. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 47961a4bb9515..1653d6f144cbd 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -335,6 +335,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Não é possível aplicar as alterações – erro inesperado: '{0}' + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range Não é possível incluir a classe \{0} no intervalo de caracteres @@ -1260,6 +1265,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Nenhuma seleção válida para realizar a extração. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 7ccd99d31968f..649d54ec65dfe 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -335,6 +335,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Не удается применить изменения, так как возникла непредвиденная ошибка: "{0}" + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range Невозможно включить класс \{0} в диапазон символов @@ -1260,6 +1265,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Нет допустимого выделения для извлечения. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index cecaabbef26c8..966d58dddc4ee 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -335,6 +335,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Değişiklikler uygulanamıyor - beklenmeyen hata: '{0}' + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range Sınıf \{0} içeremez karakter aralığı @@ -1260,6 +1265,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Ayıklamayı gerçekleştirme için geçerli seçim yok. + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index a574fb3ed1af6..d8e90bcbe0a66 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -335,6 +335,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 无法应用更改 - 意外错误:“{0}” + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range 不能在字符范围内包含类 \{0} @@ -1260,6 +1265,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 没有执行提取的有效选择。 + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 2e28b5d25d228..b6d645a30c461 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -335,6 +335,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 無法套用變更 -- 未預期的錯誤: '{0}' + + Cannot determine valid range of statements to extract + Cannot determine valid range of statements to extract + + Cannot include class \{0} in character range 無法在字元範圍內包含類別 \{0} @@ -1260,6 +1265,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 沒有可執行擷取的有效選取範圍。 + + No valid statement range to extract + No valid statement range to extract + + Not all code paths return Not all code paths return diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicExtractMethodService.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicExtractMethodService.vb index 46be28488cb77..63a4657e27cc0 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicExtractMethodService.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicExtractMethodService.vb @@ -10,11 +10,12 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend NotInheritable Class VisualBasicExtractMethodService Inherits AbstractExtractMethodService(Of VisualBasicSelectionValidator, VisualBasicMethodExtractor, VisualBasicSelectionResult, + StatementSyntax, ExecutableStatementSyntax, ExpressionSyntax) diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.Analyzer.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.Analyzer.vb index 7960a92f288a1..1aec7a6a11f52 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.Analyzer.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.Analyzer.vb @@ -2,6 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.ExtractMethod @@ -9,61 +10,61 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Private Class VisualBasicAnalyzer - Inherits Analyzer + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Private Class VisualBasicAnalyzer + Inherits Analyzer - Private Shared ReadOnly s_nonNoisySyntaxKindSet As HashSet(Of Integer) = New HashSet(Of Integer) From {SyntaxKind.WhitespaceTrivia, SyntaxKind.EndOfLineTrivia} + Public Shared Function AnalyzeResult(currentSelectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) As AnalyzerResult + Dim analyzer = New VisualBasicAnalyzer(currentSelectionResult, cancellationToken) + Return analyzer.Analyze() + End Function - Public Shared Function AnalyzeResult(currentSelectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) As AnalyzerResult - Dim analyzer = New VisualBasicAnalyzer(currentSelectionResult, cancellationToken) - Return analyzer.Analyze() - End Function + Public Sub New(currentSelectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) + MyBase.New(currentSelectionResult, localFunction:=False, cancellationToken) + End Sub - Public Sub New(currentSelectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) - MyBase.New(currentSelectionResult, localFunction:=False, cancellationToken) - End Sub + Protected Overrides ReadOnly Property TreatOutAsRef As Boolean = True - Protected Overrides ReadOnly Property TreatOutAsRef As Boolean = True + Protected Overrides Function IsInPrimaryConstructorBaseType() As Boolean + Return False + End Function - Protected Overrides Function IsInPrimaryConstructorBaseType() As Boolean - Return False - End Function + Protected Overrides Function CreateFromSymbol( + symbol As ISymbol, + type As ITypeSymbol, + style As VariableStyle, + requiresDeclarationExpressionRewrite As Boolean) As VariableInfo + If symbol.IsFunctionValue() AndAlso style.ParameterStyle.DeclarationBehavior <> DeclarationBehavior.None Then + Contract.ThrowIfFalse(style.ParameterStyle.DeclarationBehavior = DeclarationBehavior.MoveIn OrElse style.ParameterStyle.DeclarationBehavior = DeclarationBehavior.SplitIn) + style = AlwaysReturn(style) + End If - Protected Overrides Function CreateFromSymbol( - symbol As ISymbol, - type As ITypeSymbol, - style As VariableStyle, - requiresDeclarationExpressionRewrite As Boolean) As VariableInfo - If symbol.IsFunctionValue() AndAlso style.ParameterStyle.DeclarationBehavior <> DeclarationBehavior.None Then - Contract.ThrowIfFalse(style.ParameterStyle.DeclarationBehavior = DeclarationBehavior.MoveIn OrElse style.ParameterStyle.DeclarationBehavior = DeclarationBehavior.SplitIn) - style = AlwaysReturn(style) - End If + Return CreateFromSymbolCommon(symbol, type, style) + End Function - Return CreateFromSymbolCommon(Of LocalDeclarationStatementSyntax)(symbol, type, style, s_nonNoisySyntaxKindSet) - End Function + Protected Overrides Function GetRangeVariableType(symbol As IRangeVariableSymbol) As ITypeSymbol + Dim info = Me.SemanticModel.GetSpeculativeTypeInfo(Me.SelectionResult.FinalSpan.Start, SyntaxFactory.ParseName(symbol.Name), SpeculativeBindingOption.BindAsExpression) + If info.Type.IsErrorType() Then + Return Nothing + End If - Protected Overrides Function GetRangeVariableType(semanticModel As SemanticModel, symbol As IRangeVariableSymbol) As ITypeSymbol - Dim info = semanticModel.GetSpeculativeTypeInfo(Me.SelectionResult.FinalSpan.Start, SyntaxFactory.ParseName(symbol.Name), SpeculativeBindingOption.BindAsExpression) - If info.Type.IsErrorType() Then - Return Nothing - End If + Return If(info.ConvertedType.IsObjectType(), info.ConvertedType, info.Type) + End Function - Return If(info.ConvertedType.IsObjectType(), info.ConvertedType, info.Type) - End Function + Protected Overrides Function ContainsReturnStatementInSelectedCode(exitPoints As ImmutableArray(Of SyntaxNode)) As Boolean + Return exitPoints.Any(Function(n) TypeOf n Is ReturnStatementSyntax OrElse TypeOf n Is ExitStatementSyntax) + End Function - Protected Overrides Function ContainsReturnStatementInSelectedCode(jumpOutOfRegionStatements As IEnumerable(Of SyntaxNode)) As Boolean - Return jumpOutOfRegionStatements.Where(Function(n) TypeOf n Is ReturnStatementSyntax OrElse TypeOf n Is ExitStatementSyntax).Any() - End Function + Protected Overrides Function ReadOnlyFieldAllowed() As Boolean + Dim methodBlock = Me.SelectionResult.GetContainingScopeOf(Of MethodBlockBaseSyntax)() + If methodBlock Is Nothing Then + Return True + End If - Protected Overrides Function ReadOnlyFieldAllowed() As Boolean - Dim methodBlock = Me.SelectionResult.GetContainingScopeOf(Of MethodBlockBaseSyntax)() - If methodBlock Is Nothing Then - Return True - End If - - Return Not TypeOf methodBlock.BlockStatement Is SubNewStatementSyntax - End Function + Return Not TypeOf methodBlock.BlockStatement Is SubNewStatementSyntax + End Function + End Class End Class End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.PostProcessor.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.PostProcessor.vb index 07ba4e387b4c9..fd326978e6a6f 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.PostProcessor.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.PostProcessor.vb @@ -8,242 +8,244 @@ Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Private Class PostProcessor - Private ReadOnly _semanticModel As SemanticModel - Private ReadOnly _contextPosition As Integer + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Private Class PostProcessor + Private ReadOnly _semanticModel As SemanticModel + Private ReadOnly _contextPosition As Integer + + Public Sub New(semanticModel As SemanticModel, contextPosition As Integer) + Contract.ThrowIfNull(semanticModel) + + Me._semanticModel = semanticModel + Me._contextPosition = contextPosition + End Sub + + Public Function MergeDeclarationStatements(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax) + If statements.FirstOrDefault() Is Nothing Then + Return statements + End If - Public Sub New(semanticModel As SemanticModel, contextPosition As Integer) - Contract.ThrowIfNull(semanticModel) + Return MergeDeclarationStatementsWorker(statements) + End Function - Me._semanticModel = semanticModel - Me._contextPosition = contextPosition - End Sub + Private Function MergeDeclarationStatementsWorker(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax) + Dim declarationStatements = New List(Of StatementSyntax)() - Public Function MergeDeclarationStatements(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax) - If statements.FirstOrDefault() Is Nothing Then - Return statements - End If + Dim map = New Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax))() + For Each statement In statements + If Not IsDeclarationMergable(statement) Then + For Each declStatement In GetMergedDeclarationStatements(map) + declarationStatements.Add(declStatement) + Next declStatement - Return MergeDeclarationStatementsWorker(statements) - End Function + declarationStatements.Add(statement) + Continue For + End If - Private Function MergeDeclarationStatementsWorker(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax) - Dim declarationStatements = New List(Of StatementSyntax)() + AppendDeclarationStatementToMap(TryCast(statement, LocalDeclarationStatementSyntax), map) + Next statement - Dim map = New Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax))() - For Each statement In statements - If Not IsDeclarationMergable(statement) Then + ' merge leftover + If map.Count > 0 Then For Each declStatement In GetMergedDeclarationStatements(map) declarationStatements.Add(declStatement) Next declStatement - - declarationStatements.Add(statement) - Continue For End If - AppendDeclarationStatementToMap(TryCast(statement, LocalDeclarationStatementSyntax), map) - Next statement - - ' merge leftover - If map.Count > 0 Then - For Each declStatement In GetMergedDeclarationStatements(map) - declarationStatements.Add(declStatement) - Next declStatement - End If - - Return declarationStatements.ToImmutableArray() - End Function - - Private Sub AppendDeclarationStatementToMap(statement As LocalDeclarationStatementSyntax, map As Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax))) - Contract.ThrowIfNull(statement) - Contract.ThrowIfFalse(statement.Declarators.Count = 1) - - Dim declarator = statement.Declarators(0) - Dim symbolInfo = Me._semanticModel.GetSpeculativeSymbolInfo(Me._contextPosition, declarator.AsClause.Type, SpeculativeBindingOption.BindAsTypeOrNamespace) - Dim type = TryCast(symbolInfo.Symbol, ITypeSymbol) - Contract.ThrowIfNull(type) - - map.GetOrAdd(type, Function() New List(Of LocalDeclarationStatementSyntax)()).Add(statement) - End Sub + Return declarationStatements.ToImmutableArray() + End Function + + Private Sub AppendDeclarationStatementToMap(statement As LocalDeclarationStatementSyntax, map As Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax))) + Contract.ThrowIfNull(statement) + Contract.ThrowIfFalse(statement.Declarators.Count = 1) + + Dim declarator = statement.Declarators(0) + Dim symbolInfo = Me._semanticModel.GetSpeculativeSymbolInfo(Me._contextPosition, declarator.AsClause.Type, SpeculativeBindingOption.BindAsTypeOrNamespace) + Dim type = TryCast(symbolInfo.Symbol, ITypeSymbol) + Contract.ThrowIfNull(type) + + map.GetOrAdd(type, Function() New List(Of LocalDeclarationStatementSyntax)()).Add(statement) + End Sub + + Private Shared Function GetMergedDeclarationStatements(map As Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax))) As IEnumerable(Of LocalDeclarationStatementSyntax) + Dim declarationStatements = New List(Of LocalDeclarationStatementSyntax)() + + For Each keyValuePair In map + Contract.ThrowIfFalse(keyValuePair.Value.Count > 0) + + ' merge all variable decl for current type + Dim variables = New List(Of ModifiedIdentifierSyntax)() + For Each statement In keyValuePair.Value + For Each variable In statement.Declarators(0).Names + variables.Add(variable) + Next variable + Next statement + + ' and create one decl statement + ' use type name from the first decl statement + Dim firstDeclaration = keyValuePair.Value.First() + declarationStatements.Add( + SyntaxFactory.LocalDeclarationStatement(firstDeclaration.Modifiers, + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.VariableDeclarator(SyntaxFactory.SeparatedList(variables)).WithAsClause(firstDeclaration.Declarators(0).AsClause)) + )) + Next keyValuePair + + map.Clear() + + Return declarationStatements + End Function + + Private Function IsDeclarationMergable(statement As StatementSyntax) As Boolean + Contract.ThrowIfNull(statement) + + ' to be mergable, statement must be + ' 1. decl statement without any extra info + ' 2. no initialization on any of its decls + ' 3. no trivia except whitespace + ' 4. type must be known + + Dim declarationStatement = TryCast(statement, LocalDeclarationStatementSyntax) + If declarationStatement Is Nothing Then + Return False + End If - Private Shared Function GetMergedDeclarationStatements(map As Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax))) As IEnumerable(Of LocalDeclarationStatementSyntax) - Dim declarationStatements = New List(Of LocalDeclarationStatementSyntax)() + If declarationStatement.Modifiers.Any(SyntaxKind.ConstKeyword) OrElse + declarationStatement.IsMissing Then + Return False + End If - For Each keyValuePair In map - Contract.ThrowIfFalse(keyValuePair.Value.Count > 0) + If ContainsAnyInitialization(declarationStatement) Then + Return False + End If - ' merge all variable decl for current type - Dim variables = New List(Of ModifiedIdentifierSyntax)() - For Each statement In keyValuePair.Value - For Each variable In statement.Declarators(0).Names - variables.Add(variable) - Next variable - Next statement + If Not ContainsOnlyWhitespaceTrivia(declarationStatement) Then + Return False + End If - ' and create one decl statement - ' use type name from the first decl statement - Dim firstDeclaration = keyValuePair.Value.First() - declarationStatements.Add( - SyntaxFactory.LocalDeclarationStatement(firstDeclaration.Modifiers, - SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.VariableDeclarator(SyntaxFactory.SeparatedList(variables)).WithAsClause(firstDeclaration.Declarators(0).AsClause)) - )) - Next keyValuePair + If declarationStatement.Declarators.Count <> 1 Then + Return False + End If - map.Clear() + If declarationStatement.Declarators(0).AsClause Is Nothing Then + Return False + End If - Return declarationStatements - End Function + Dim symbolInfo = Me._semanticModel.GetSpeculativeSymbolInfo(Me._contextPosition, declarationStatement.Declarators(0).AsClause.Type, SpeculativeBindingOption.BindAsTypeOrNamespace) + Dim type = TryCast(symbolInfo.Symbol, ITypeSymbol) + If type Is Nothing OrElse + type.TypeKind = TypeKind.Error OrElse + type.TypeKind = TypeKind.Unknown Then + Return False + End If - Private Function IsDeclarationMergable(statement As StatementSyntax) As Boolean - Contract.ThrowIfNull(statement) + Return True + End Function - ' to be mergable, statement must be - ' 1. decl statement without any extra info - ' 2. no initialization on any of its decls - ' 3. no trivia except whitespace - ' 4. type must be known + Private Shared Function ContainsAnyInitialization(statement As LocalDeclarationStatementSyntax) As Boolean + For Each variable In statement.Declarators + If variable.Initializer IsNot Nothing Then + Return True + End If + Next variable - Dim declarationStatement = TryCast(statement, LocalDeclarationStatementSyntax) - If declarationStatement Is Nothing Then Return False - End If + End Function + + Private Shared Function ContainsOnlyWhitespaceTrivia(statement As StatementSyntax) As Boolean + For Each token In statement.DescendantTokens() + If Not ContainsOnlyWhitespaceTrivia(token) Then + Return False + End If + Next token + + Return True + End Function + + Private Shared Function ContainsOnlyWhitespaceTrivia(token As SyntaxToken) As Boolean + For Each trivia In token.LeadingTrivia.Concat(token.TrailingTrivia) + If trivia.Kind <> SyntaxKind.WhitespaceTrivia AndAlso trivia.Kind <> SyntaxKind.EndOfLineTrivia Then + Return False + End If + Next trivia + + Return True + End Function + + Public Shared Function RemoveDeclarationAssignmentPattern(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax) + If statements.Count() < 2 Then + Return statements + End If - If declarationStatement.Modifiers.Any(SyntaxKind.ConstKeyword) OrElse - declarationStatement.IsMissing Then - Return False - End If + ' if we have inline temp variable as service, we could just use that service here. + ' since it is not a service right now, do very simple clean up + Dim declaration = TryCast(statements(0), LocalDeclarationStatementSyntax) + Dim assignment = TryCast(statements(1), AssignmentStatementSyntax) + If declaration Is Nothing OrElse assignment Is Nothing Then + Return statements + End If - If ContainsAnyInitialization(declarationStatement) Then - Return False - End If + If ContainsAnyInitialization(declaration) OrElse + declaration.Modifiers.Any(Function(m) m.Kind <> SyntaxKind.DimKeyword) OrElse + declaration.Declarators.Count <> 1 OrElse + declaration.Declarators(0).Names.Count <> 1 OrElse + assignment.Left Is Nothing OrElse + assignment.Right Is Nothing Then + Return statements + End If - If Not ContainsOnlyWhitespaceTrivia(declarationStatement) Then - Return False - End If + If Not ContainsOnlyWhitespaceTrivia(declaration) OrElse + Not ContainsOnlyWhitespaceTrivia(assignment) Then + Return statements + End If - If declarationStatement.Declarators.Count <> 1 Then - Return False - End If + Dim variableName = declaration.Declarators(0).Names(0).ToString() - If declarationStatement.Declarators(0).AsClause Is Nothing Then - Return False - End If + If assignment.Left.ToString() <> variableName Then + Return statements + End If - Dim symbolInfo = Me._semanticModel.GetSpeculativeSymbolInfo(Me._contextPosition, declarationStatement.Declarators(0).AsClause.Type, SpeculativeBindingOption.BindAsTypeOrNamespace) - Dim type = TryCast(symbolInfo.Symbol, ITypeSymbol) - If type Is Nothing OrElse - type.TypeKind = TypeKind.Error OrElse - type.TypeKind = TypeKind.Unknown Then - Return False - End If + Dim variable = declaration.Declarators(0).WithoutTrailingTrivia().WithInitializer(SyntaxFactory.EqualsValue(assignment.Right)) + Dim newDeclaration = declaration.WithDeclarators(SyntaxFactory.SingletonSeparatedList(variable)) - Return True - End Function + Return SpecializedCollections.SingletonEnumerable(Of StatementSyntax)(newDeclaration).Concat(statements.Skip(2)).ToImmutableArray() + End Function - Private Shared Function ContainsAnyInitialization(statement As LocalDeclarationStatementSyntax) As Boolean - For Each variable In statement.Declarators - If variable.Initializer IsNot Nothing Then - Return True + Public Shared Function RemoveInitializedDeclarationAndReturnPattern(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax) + ' if we have inline temp variable as service, we could just use that service here. + ' since it is not a service right now, do very simple clean up + If statements.Count() <> 2 Then + Return statements End If - Next variable - Return False - End Function + Dim declaration = TryCast(statements.ElementAtOrDefault(0), LocalDeclarationStatementSyntax) + Dim returnStatement = TryCast(statements.ElementAtOrDefault(1), ReturnStatementSyntax) + If declaration Is Nothing OrElse returnStatement Is Nothing Then + Return statements + End If - Private Shared Function ContainsOnlyWhitespaceTrivia(statement As StatementSyntax) As Boolean - For Each token In statement.DescendantTokens() - If Not ContainsOnlyWhitespaceTrivia(token) Then - Return False + If declaration.Declarators.Count <> 1 OrElse + declaration.Declarators(0).Names.Count <> 1 OrElse + declaration.Declarators(0).Initializer Is Nothing OrElse + returnStatement.Expression Is Nothing Then + Return statements End If - Next token - Return True - End Function + If Not ContainsOnlyWhitespaceTrivia(declaration) OrElse + Not ContainsOnlyWhitespaceTrivia(returnStatement) Then + Return statements + End If - Private Shared Function ContainsOnlyWhitespaceTrivia(token As SyntaxToken) As Boolean - For Each trivia In token.LeadingTrivia.Concat(token.TrailingTrivia) - If trivia.Kind <> SyntaxKind.WhitespaceTrivia AndAlso trivia.Kind <> SyntaxKind.EndOfLineTrivia Then - Return False + Dim variableName = declaration.Declarators(0).Names(0).ToString() + If returnStatement.Expression.ToString() <> variableName Then + Return statements End If - Next trivia - - Return True - End Function - - Public Shared Function RemoveDeclarationAssignmentPattern(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax) - If statements.Count() < 2 Then - Return statements - End If - - ' if we have inline temp variable as service, we could just use that service here. - ' since it is not a service right now, do very simple clean up - Dim declaration = TryCast(statements(0), LocalDeclarationStatementSyntax) - Dim assignment = TryCast(statements(1), AssignmentStatementSyntax) - If declaration Is Nothing OrElse assignment Is Nothing Then - Return statements - End If - - If ContainsAnyInitialization(declaration) OrElse - declaration.Modifiers.Any(Function(m) m.Kind <> SyntaxKind.DimKeyword) OrElse - declaration.Declarators.Count <> 1 OrElse - declaration.Declarators(0).Names.Count <> 1 OrElse - assignment.Left Is Nothing OrElse - assignment.Right Is Nothing Then - Return statements - End If - - If Not ContainsOnlyWhitespaceTrivia(declaration) OrElse - Not ContainsOnlyWhitespaceTrivia(assignment) Then - Return statements - End If - - Dim variableName = declaration.Declarators(0).Names(0).ToString() - - If assignment.Left.ToString() <> variableName Then - Return statements - End If - - Dim variable = declaration.Declarators(0).WithoutTrailingTrivia().WithInitializer(SyntaxFactory.EqualsValue(assignment.Right)) - Dim newDeclaration = declaration.WithDeclarators(SyntaxFactory.SingletonSeparatedList(variable)) - - Return SpecializedCollections.SingletonEnumerable(Of StatementSyntax)(newDeclaration).Concat(statements.Skip(2)).ToImmutableArray() - End Function - - Public Shared Function RemoveInitializedDeclarationAndReturnPattern(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax) - ' if we have inline temp variable as service, we could just use that service here. - ' since it is not a service right now, do very simple clean up - If statements.Count() <> 2 Then - Return statements - End If - - Dim declaration = TryCast(statements.ElementAtOrDefault(0), LocalDeclarationStatementSyntax) - Dim returnStatement = TryCast(statements.ElementAtOrDefault(1), ReturnStatementSyntax) - If declaration Is Nothing OrElse returnStatement Is Nothing Then - Return statements - End If - - If declaration.Declarators.Count <> 1 OrElse - declaration.Declarators(0).Names.Count <> 1 OrElse - declaration.Declarators(0).Initializer Is Nothing OrElse - returnStatement.Expression Is Nothing Then - Return statements - End If - - If Not ContainsOnlyWhitespaceTrivia(declaration) OrElse - Not ContainsOnlyWhitespaceTrivia(returnStatement) Then - Return statements - End If - - Dim variableName = declaration.Declarators(0).Names(0).ToString() - If returnStatement.Expression.ToString() <> variableName Then - Return statements - End If - - Return SpecializedCollections.SingletonEnumerable(Of StatementSyntax)( - SyntaxFactory.ReturnStatement(declaration.Declarators(0).Initializer.Value)).Concat(statements.Skip(2)).ToImmutableArray() - End Function + + Return SpecializedCollections.SingletonEnumerable(Of StatementSyntax)( + SyntaxFactory.ReturnStatement(declaration.Declarators(0).Initializer.Value)).Concat(statements.Skip(2)).ToImmutableArray() + End Function + End Class End Class End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.TriviaResult.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.TriviaResult.vb index 7b0a3b0b0dcec..79bdde6c6f821 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.TriviaResult.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.TriviaResult.vb @@ -8,197 +8,199 @@ Imports Microsoft.CodeAnalysis.ExtractMethod Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Private Class VisualBasicTriviaResult - Inherits TriviaResult - - Public Shared Async Function ProcessAsync(selectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) As Task(Of VisualBasicTriviaResult) - Dim preservationService = selectionResult.SemanticDocument.Document.Project.Services.GetService(Of ISyntaxTriviaService)() - Dim root = selectionResult.SemanticDocument.Root - Dim result = preservationService.SaveTriviaAroundSelection(root, selectionResult.FinalSpan) - - Return New VisualBasicTriviaResult( - Await selectionResult.SemanticDocument.WithSyntaxRootAsync(result.Root, cancellationToken).ConfigureAwait(False), - result) - End Function - - Private Sub New(document As SemanticDocument, result As ITriviaSavedResult) - MyBase.New(document, result, SyntaxKind.EndOfLineTrivia, SyntaxKind.WhitespaceTrivia) - End Sub - - Protected Overrides Function GetAnnotationResolver(callsite As SyntaxNode, method As SyntaxNode) As AnnotationResolver - Dim methodDefinition = TryCast(method, MethodBlockBaseSyntax) - If callsite Is Nothing OrElse methodDefinition Is Nothing Then - Return Nothing - End If - - Return Function(node, location, annotation) AnnotationResolver(node, location, annotation, callsite, methodDefinition) - End Function - - Protected Overrides Function GetTriviaResolver(method As SyntaxNode) As TriviaResolver - Dim methodDefinition = TryCast(method, MethodBlockBaseSyntax) - If methodDefinition Is Nothing Then - Return Nothing - End If - - Return Function(location, tokenPair, triviaMap) TriviaResolver(location, tokenPair, triviaMap, methodDefinition) - End Function - - Private Shared Function AnnotationResolver( - node As SyntaxNode, - location As TriviaLocation, - annotation As SyntaxAnnotation, - callsite As SyntaxNode, - method As MethodBlockBaseSyntax) As SyntaxToken - - Dim token = node.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault().AsToken() - If token.Kind <> 0 Then - Return token - End If - - Select Case location - Case TriviaLocation.BeforeBeginningOfSpan - Return callsite.GetFirstToken(includeZeroWidth:=True).GetPreviousToken(includeZeroWidth:=True) - Case TriviaLocation.AfterEndOfSpan - Return callsite.GetLastToken(includeZeroWidth:=True).GetNextToken(includeZeroWidth:=True) - Case TriviaLocation.AfterBeginningOfSpan - Return method.BlockStatement.GetLastToken(includeZeroWidth:=True).GetNextToken(includeZeroWidth:=True) - Case TriviaLocation.BeforeEndOfSpan - Return method.EndBlockStatement.GetFirstToken(includeZeroWidth:=True).GetPreviousToken(includeZeroWidth:=True) - End Select - - throw ExceptionUtilities.UnexpectedValue(location) - End Function - - Private Function TriviaResolver( - location As TriviaLocation, - tokenPair As PreviousNextTokenPair, - triviaMap As Dictionary(Of SyntaxToken, LeadingTrailingTriviaPair), - method As MethodBlockBaseSyntax) As IEnumerable(Of SyntaxTrivia) - - ' Resolve trivia at the edge of the selection. simple case is easy to deal with, but complex cases where - ' elastic trivia and user trivia are mixed (hybrid case) and we want to preserve some part of user coding style - ' but not others can be dealt with here. - - ' method has no statement in them. so basically two trivia list now pointing to same thing. - If tokenPair.PreviousToken = method.BlockStatement.GetLastToken(includeZeroWidth:=True) AndAlso - tokenPair.NextToken = method.EndBlockStatement.GetFirstToken(includeZeroWidth:=True) Then - Return If(location = TriviaLocation.AfterBeginningOfSpan, - SpecializedCollections.SingletonEnumerable(Of SyntaxTrivia)(SyntaxFactory.ElasticMarker), - SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)()) - End If - - Dim previousTriviaPair As LeadingTrailingTriviaPair = Nothing - Dim trailingTrivia = If(triviaMap.TryGetValue(tokenPair.PreviousToken, previousTriviaPair), - previousTriviaPair.TrailingTrivia, SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)()) - - Dim nextTriviaPair As LeadingTrailingTriviaPair = Nothing - Dim leadingTrivia = If(triviaMap.TryGetValue(tokenPair.NextToken, nextTriviaPair), - nextTriviaPair.LeadingTrivia, SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)()) - - Dim list = trailingTrivia.Concat(leadingTrivia) - - Select Case location - Case TriviaLocation.BeforeBeginningOfSpan - Return FilterTriviaList(RemoveTrailingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken)) - Case TriviaLocation.AfterEndOfSpan - Return FilterTriviaList(RemoveLeadingElasticTrivia(RemoveLeadingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken))) - Case TriviaLocation.AfterBeginningOfSpan - Return FilterTriviaList(RemoveLeadingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken)) - Case TriviaLocation.BeforeEndOfSpan - Return FilterTriviaList(RemoveTrailingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken)) - End Select - - throw ExceptionUtilities.UnexpectedValue(location) - End Function - - Private Shared Function RemoveTrailingElasticTrivia( - token1 As SyntaxToken, list As IEnumerable(Of SyntaxTrivia), token2 As SyntaxToken) As IEnumerable(Of SyntaxTrivia) - - ' special case for skipped token trivia - ' formatter doesn't touch tokens that have skipped tokens in-between. so, we need to take care of such case ourselves - If list.Any(Function(t) t.RawKind = SyntaxKind.SkippedTokensTrivia) Then - Return RemoveElasticAfterColon( - token1.TrailingTrivia.Concat(list).Concat(ReplaceElasticToEndOfLine(token2.LeadingTrivia))) - End If - - If token1.IsLastTokenOfStatement() Then - Return RemoveElasticAfterColon(token1.TrailingTrivia.Concat(list).Concat(token2.LeadingTrivia)) - End If - - Return token1.TrailingTrivia.Concat(list) - End Function - - Private Shared Function RemoveLeadingElasticTrivia( - token1 As SyntaxToken, list As IEnumerable(Of SyntaxTrivia), token2 As SyntaxToken) As IEnumerable(Of SyntaxTrivia) - - If token1.IsLastTokenOfStatement() Then - If SingleLineStatement(token1) Then - Return list.Concat(token2.LeadingTrivia) + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Private Class VisualBasicTriviaResult + Inherits TriviaResult + + Public Shared Async Function ProcessAsync(selectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) As Task(Of VisualBasicTriviaResult) + Dim preservationService = selectionResult.SemanticDocument.Document.Project.Services.GetService(Of ISyntaxTriviaService)() + Dim root = selectionResult.SemanticDocument.Root + Dim result = preservationService.SaveTriviaAroundSelection(root, selectionResult.FinalSpan) + + Return New VisualBasicTriviaResult( + Await selectionResult.SemanticDocument.WithSyntaxRootAsync(result.Root, cancellationToken).ConfigureAwait(False), + result) + End Function + + Private Sub New(document As SemanticDocument, result As ITriviaSavedResult) + MyBase.New(document, result, SyntaxKind.EndOfLineTrivia, SyntaxKind.WhitespaceTrivia) + End Sub + + Protected Overrides Function GetAnnotationResolver(callsite As SyntaxNode, method As SyntaxNode) As AnnotationResolver + Dim methodDefinition = TryCast(method, MethodBlockBaseSyntax) + If callsite Is Nothing OrElse methodDefinition Is Nothing Then + Return Nothing End If - Return RemoveElasticAfterColon(token1.TrailingTrivia.Concat(list).Concat(token2.LeadingTrivia)) - End If + Return Function(node, location, annotation) AnnotationResolver(node, location, annotation, callsite, methodDefinition) + End Function - Return list.Concat(token2.LeadingTrivia) - End Function + Protected Overrides Function GetTriviaResolver(method As SyntaxNode) As TriviaResolver + Dim methodDefinition = TryCast(method, MethodBlockBaseSyntax) + If methodDefinition Is Nothing Then + Return Nothing + End If - Private Shared Function RemoveLeadingElasticTrivia(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) - ' remove leading elastic trivia if it is followed by noisy trivia - Dim trivia = list.FirstOrDefault() - If Not trivia.IsElastic() Then - Return list - End If + Return Function(location, tokenPair, triviaMap) TriviaResolver(location, tokenPair, triviaMap, methodDefinition) + End Function + + Private Shared Function AnnotationResolver( + node As SyntaxNode, + location As TriviaLocation, + annotation As SyntaxAnnotation, + callsite As SyntaxNode, + method As MethodBlockBaseSyntax) As SyntaxToken + + Dim token = node.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault().AsToken() + If token.Kind <> 0 Then + Return token + End If + + Select Case location + Case TriviaLocation.BeforeBeginningOfSpan + Return callsite.GetFirstToken(includeZeroWidth:=True).GetPreviousToken(includeZeroWidth:=True) + Case TriviaLocation.AfterEndOfSpan + Return callsite.GetLastToken(includeZeroWidth:=True).GetNextToken(includeZeroWidth:=True) + Case TriviaLocation.AfterBeginningOfSpan + Return method.BlockStatement.GetLastToken(includeZeroWidth:=True).GetNextToken(includeZeroWidth:=True) + Case TriviaLocation.BeforeEndOfSpan + Return method.EndBlockStatement.GetFirstToken(includeZeroWidth:=True).GetPreviousToken(includeZeroWidth:=True) + End Select + + Throw ExceptionUtilities.UnexpectedValue(location) + End Function + + Private Function TriviaResolver( + location As TriviaLocation, + tokenPair As PreviousNextTokenPair, + triviaMap As Dictionary(Of SyntaxToken, LeadingTrailingTriviaPair), + method As MethodBlockBaseSyntax) As IEnumerable(Of SyntaxTrivia) + + ' Resolve trivia at the edge of the selection. simple case is easy to deal with, but complex cases where + ' elastic trivia and user trivia are mixed (hybrid case) and we want to preserve some part of user coding style + ' but not others can be dealt with here. + + ' method has no statement in them. so basically two trivia list now pointing to same thing. + If tokenPair.PreviousToken = method.BlockStatement.GetLastToken(includeZeroWidth:=True) AndAlso + tokenPair.NextToken = method.EndBlockStatement.GetFirstToken(includeZeroWidth:=True) Then + Return If(location = TriviaLocation.AfterBeginningOfSpan, + SpecializedCollections.SingletonEnumerable(Of SyntaxTrivia)(SyntaxFactory.ElasticMarker), + SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)()) + End If + + Dim previousTriviaPair As LeadingTrailingTriviaPair = Nothing + Dim trailingTrivia = If(triviaMap.TryGetValue(tokenPair.PreviousToken, previousTriviaPair), + previousTriviaPair.TrailingTrivia, SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)()) + + Dim nextTriviaPair As LeadingTrailingTriviaPair = Nothing + Dim leadingTrivia = If(triviaMap.TryGetValue(tokenPair.NextToken, nextTriviaPair), + nextTriviaPair.LeadingTrivia, SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)()) + + Dim list = trailingTrivia.Concat(leadingTrivia) + + Select Case location + Case TriviaLocation.BeforeBeginningOfSpan + Return FilterTriviaList(RemoveTrailingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken)) + Case TriviaLocation.AfterEndOfSpan + Return FilterTriviaList(RemoveLeadingElasticTrivia(RemoveLeadingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken))) + Case TriviaLocation.AfterBeginningOfSpan + Return FilterTriviaList(RemoveLeadingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken)) + Case TriviaLocation.BeforeEndOfSpan + Return FilterTriviaList(RemoveTrailingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken)) + End Select + + Throw ExceptionUtilities.UnexpectedValue(location) + End Function + + Private Shared Function RemoveTrailingElasticTrivia( + token1 As SyntaxToken, list As IEnumerable(Of SyntaxTrivia), token2 As SyntaxToken) As IEnumerable(Of SyntaxTrivia) + + ' special case for skipped token trivia + ' formatter doesn't touch tokens that have skipped tokens in-between. so, we need to take care of such case ourselves + If list.Any(Function(t) t.RawKind = SyntaxKind.SkippedTokensTrivia) Then + Return RemoveElasticAfterColon( + token1.TrailingTrivia.Concat(list).Concat(ReplaceElasticToEndOfLine(token2.LeadingTrivia))) + End If + + If token1.IsLastTokenOfStatement() Then + Return RemoveElasticAfterColon(token1.TrailingTrivia.Concat(list).Concat(token2.LeadingTrivia)) + End If + + Return token1.TrailingTrivia.Concat(list) + End Function + + Private Shared Function RemoveLeadingElasticTrivia( + token1 As SyntaxToken, list As IEnumerable(Of SyntaxTrivia), token2 As SyntaxToken) As IEnumerable(Of SyntaxTrivia) - For Each trivia In list.Skip(1) - If trivia.Kind = SyntaxKind.EndOfLineTrivia OrElse trivia.IsElastic() Then + If token1.IsLastTokenOfStatement() Then + If SingleLineStatement(token1) Then + Return list.Concat(token2.LeadingTrivia) + End If + + Return RemoveElasticAfterColon(token1.TrailingTrivia.Concat(list).Concat(token2.LeadingTrivia)) + End If + + Return list.Concat(token2.LeadingTrivia) + End Function + + Private Shared Function RemoveLeadingElasticTrivia(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) + ' remove leading elastic trivia if it is followed by noisy trivia + Dim trivia = list.FirstOrDefault() + If Not trivia.IsElastic() Then Return list - ElseIf trivia.Kind <> SyntaxKind.EndOfLineTrivia And trivia.Kind <> SyntaxKind.WhitespaceTrivia Then - Return list.Skip(1) End If - Next - - Return list - End Function - - Private Shared Function ReplaceElasticToEndOfLine(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) - Return list.Select(Function(t) If(t.IsElastic, SyntaxFactory.CarriageReturnLineFeed, t)) - End Function - - Private Shared Function SingleLineStatement(token As SyntaxToken) As Boolean - ' check whether given token is the last token of a single line statement - Dim singleLineIf = token.Parent.GetAncestor(Of SingleLineIfStatementSyntax)() - If singleLineIf IsNot Nothing Then - Return True - End If - - Dim singleLineLambda = token.Parent.GetAncestor(Of SingleLineLambdaExpressionSyntax)() - If singleLineLambda IsNot Nothing Then - Return True - End If - - Return False - End Function - - Private Shared Function RemoveElasticAfterColon(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) - ' make sure we don't have elastic trivia after colon trivia - Dim colon = False - Dim result = New List(Of SyntaxTrivia)() - - For Each trivia In list - If trivia.RawKind = SyntaxKind.ColonTrivia Then - colon = True + + For Each trivia In list.Skip(1) + If trivia.Kind = SyntaxKind.EndOfLineTrivia OrElse trivia.IsElastic() Then + Return list + ElseIf trivia.Kind <> SyntaxKind.EndOfLineTrivia And trivia.Kind <> SyntaxKind.WhitespaceTrivia Then + Return list.Skip(1) + End If + Next + + Return list + End Function + + Private Shared Function ReplaceElasticToEndOfLine(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) + Return list.Select(Function(t) If(t.IsElastic, SyntaxFactory.CarriageReturnLineFeed, t)) + End Function + + Private Shared Function SingleLineStatement(token As SyntaxToken) As Boolean + ' check whether given token is the last token of a single line statement + Dim singleLineIf = token.Parent.GetAncestor(Of SingleLineIfStatementSyntax)() + If singleLineIf IsNot Nothing Then + Return True End If - If colon AndAlso trivia.IsElastic() Then - Continue For + Dim singleLineLambda = token.Parent.GetAncestor(Of SingleLineLambdaExpressionSyntax)() + If singleLineLambda IsNot Nothing Then + Return True End If - result.Add(trivia) - Next + Return False + End Function + + Private Shared Function RemoveElasticAfterColon(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) + ' make sure we don't have elastic trivia after colon trivia + Dim colon = False + Dim result = New List(Of SyntaxTrivia)() + + For Each trivia In list + If trivia.RawKind = SyntaxKind.ColonTrivia Then + colon = True + End If + + If colon AndAlso trivia.IsElastic() Then + Continue For + End If + + result.Add(trivia) + Next - Return result - End Function + Return result + End Function + End Class End Class End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.CallSiteContainerRewriter.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.CallSiteContainerRewriter.vb index 81f9a65062dcf..9a4ec7a0d9db6 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.CallSiteContainerRewriter.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.CallSiteContainerRewriter.vb @@ -7,402 +7,404 @@ Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Partial Private MustInherit Class VisualBasicCodeGenerator - Private Class CallSiteContainerRewriter - Inherits VisualBasicSyntaxRewriter - Private ReadOnly _outmostCallSiteContainer As SyntaxNode - Private ReadOnly _statementsOrFieldToInsert As IEnumerable(Of StatementSyntax) - Private ReadOnly _variableToRemoveMap As HashSet(Of SyntaxAnnotation) - Private ReadOnly _firstStatementOrFieldToReplace As StatementSyntax - Private ReadOnly _lastStatementOrFieldToReplace As StatementSyntax - - Private Shared ReadOnly s_removeAnnotation As SyntaxAnnotation = New SyntaxAnnotation() - - Public Sub New(outmostCallSiteContainer As SyntaxNode, - variableToRemoveMap As HashSet(Of SyntaxAnnotation), - firstStatementOrFieldToReplace As StatementSyntax, - lastStatementOrFieldToReplace As StatementSyntax, - statementsOrFieldToInsert As IEnumerable(Of StatementSyntax)) - Contract.ThrowIfNull(outmostCallSiteContainer) - Contract.ThrowIfNull(variableToRemoveMap) - Contract.ThrowIfNull(firstStatementOrFieldToReplace) - Contract.ThrowIfNull(lastStatementOrFieldToReplace) - Contract.ThrowIfTrue(statementsOrFieldToInsert.IsEmpty()) - - Me._outmostCallSiteContainer = outmostCallSiteContainer - - Me._variableToRemoveMap = variableToRemoveMap - Me._statementsOrFieldToInsert = statementsOrFieldToInsert - - Me._firstStatementOrFieldToReplace = firstStatementOrFieldToReplace - Me._lastStatementOrFieldToReplace = lastStatementOrFieldToReplace - - Contract.ThrowIfFalse(Me._firstStatementOrFieldToReplace.Parent Is Me._lastStatementOrFieldToReplace.Parent) - End Sub - - Public Function Generate() As SyntaxNode - Dim result = Visit(Me._outmostCallSiteContainer) - - ' remove any nodes annotated for removal - If result.ContainsAnnotations Then - Dim nodesToRemove = result.DescendantNodes(Function(n) n.ContainsAnnotations).Where(Function(n) n.HasAnnotation(s_removeAnnotation)) - result = result.RemoveNodes(nodesToRemove, SyntaxRemoveOptions.KeepNoTrivia) - End If - - Return result - End Function - - Private ReadOnly Property ContainerOfStatementsOrFieldToReplace() As SyntaxNode - Get - Return Me._firstStatementOrFieldToReplace.Parent - End Get - End Property - - Public Overrides Function VisitLocalDeclarationStatement(node As LocalDeclarationStatementSyntax) As SyntaxNode - node = CType(MyBase.VisitLocalDeclarationStatement(node), LocalDeclarationStatementSyntax) - - Dim expressionStatements = New List(Of StatementSyntax)() - Dim variableDeclarators = New List(Of VariableDeclaratorSyntax)() - Dim triviaList = New List(Of SyntaxTrivia)() - - If Not Me._variableToRemoveMap.ProcessLocalDeclarationStatement(node, expressionStatements, variableDeclarators, triviaList) Then + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Partial Private MustInherit Class VisualBasicCodeGenerator + Private Class CallSiteContainerRewriter + Inherits VisualBasicSyntaxRewriter + Private ReadOnly _outmostCallSiteContainer As SyntaxNode + Private ReadOnly _statementsOrFieldToInsert As IEnumerable(Of StatementSyntax) + Private ReadOnly _variableToRemoveMap As HashSet(Of SyntaxAnnotation) + Private ReadOnly _firstStatementOrFieldToReplace As StatementSyntax + Private ReadOnly _lastStatementOrFieldToReplace As StatementSyntax + + Private Shared ReadOnly s_removeAnnotation As SyntaxAnnotation = New SyntaxAnnotation() + + Public Sub New(outmostCallSiteContainer As SyntaxNode, + variableToRemoveMap As HashSet(Of SyntaxAnnotation), + firstStatementOrFieldToReplace As StatementSyntax, + lastStatementOrFieldToReplace As StatementSyntax, + statementsOrFieldToInsert As IEnumerable(Of StatementSyntax)) + Contract.ThrowIfNull(outmostCallSiteContainer) + Contract.ThrowIfNull(variableToRemoveMap) + Contract.ThrowIfNull(firstStatementOrFieldToReplace) + Contract.ThrowIfNull(lastStatementOrFieldToReplace) + Contract.ThrowIfTrue(statementsOrFieldToInsert.IsEmpty()) + + Me._outmostCallSiteContainer = outmostCallSiteContainer + + Me._variableToRemoveMap = variableToRemoveMap + Me._statementsOrFieldToInsert = statementsOrFieldToInsert + + Me._firstStatementOrFieldToReplace = firstStatementOrFieldToReplace + Me._lastStatementOrFieldToReplace = lastStatementOrFieldToReplace + + Contract.ThrowIfFalse(Me._firstStatementOrFieldToReplace.Parent Is Me._lastStatementOrFieldToReplace.Parent) + End Sub + + Public Function Generate() As SyntaxNode + Dim result = Visit(Me._outmostCallSiteContainer) + + ' remove any nodes annotated for removal + If result.ContainsAnnotations Then + Dim nodesToRemove = result.DescendantNodes(Function(n) n.ContainsAnnotations).Where(Function(n) n.HasAnnotation(s_removeAnnotation)) + result = result.RemoveNodes(nodesToRemove, SyntaxRemoveOptions.KeepNoTrivia) + End If + + Return result + End Function + + Private ReadOnly Property ContainerOfStatementsOrFieldToReplace() As SyntaxNode + Get + Return Me._firstStatementOrFieldToReplace.Parent + End Get + End Property + + Public Overrides Function VisitLocalDeclarationStatement(node As LocalDeclarationStatementSyntax) As SyntaxNode + node = CType(MyBase.VisitLocalDeclarationStatement(node), LocalDeclarationStatementSyntax) + + Dim expressionStatements = New List(Of StatementSyntax)() + Dim variableDeclarators = New List(Of VariableDeclaratorSyntax)() + Dim triviaList = New List(Of SyntaxTrivia)() + + If Not Me._variableToRemoveMap.ProcessLocalDeclarationStatement(node, expressionStatements, variableDeclarators, triviaList) Then + Contract.ThrowIfFalse(expressionStatements.Count = 0) + Return node + End If + Contract.ThrowIfFalse(expressionStatements.Count = 0) - Return node - End If - - Contract.ThrowIfFalse(expressionStatements.Count = 0) - - If variableDeclarators.Count = 0 AndAlso - triviaList.Any(Function(t) t.Kind <> SyntaxKind.WhitespaceTrivia AndAlso t.Kind <> SyntaxKind.EndOfLineTrivia) Then - ' well, there are trivia associated with the node. - ' we can't just delete the node since then, we will lose - ' the trivia. unfortunately, it is not easy to attach the trivia - ' to next token. for now, create an empty statement and associate the - ' trivia to the statement - - ' TODO : think about a way to trivia attached to next token - Return SyntaxFactory.EmptyStatement(SyntaxFactory.Token(SyntaxKind.EmptyToken).WithLeadingTrivia(SyntaxFactory.TriviaList(triviaList))) - End If - - ' return survived var decls - If variableDeclarators.Count > 0 Then - Return SyntaxFactory.LocalDeclarationStatement( - node.Modifiers, - SyntaxFactory.SeparatedList(variableDeclarators)).WithPrependedLeadingTrivia(triviaList) - End If - - Return node.WithAdditionalAnnotations(s_removeAnnotation) - End Function - - Public Overrides Function VisitMethodBlock(node As MethodBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the block - Return MyBase.VisitMethodBlock(node) - End If - - Return node.WithSubOrFunctionStatement(ReplaceStatementIfNeeded(node.SubOrFunctionStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitConstructorBlock(node As ConstructorBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the block - Return MyBase.VisitConstructorBlock(node) - End If - - Return node.WithSubNewStatement(ReplaceStatementIfNeeded(node.SubNewStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitOperatorBlock(node As OperatorBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the block - Return MyBase.VisitOperatorBlock(node) - End If - - Return node.WithOperatorStatement(ReplaceStatementIfNeeded(node.OperatorStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitAccessorBlock(node As AccessorBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the block - Return MyBase.VisitAccessorBlock(node) - End If - - Return node.WithAccessorStatement(ReplaceStatementIfNeeded(node.AccessorStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitWhileBlock(node As WhileBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the switch section - Return MyBase.VisitWhileBlock(node) - End If - - Return node.WithWhileStatement(ReplaceStatementIfNeeded(node.WhileStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitUsingBlock(node As UsingBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitUsingBlock(node) - End If - - Return node.WithUsingStatement(ReplaceStatementIfNeeded(node.UsingStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitSyncLockBlock(node As SyncLockBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitSyncLockBlock(node) - End If - - Return node.WithSyncLockStatement(ReplaceStatementIfNeeded(node.SyncLockStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitWithBlock(node As WithBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitWithBlock(node) - End If - - Return node.WithWithStatement(ReplaceStatementIfNeeded(node.WithStatement)). - WithStatements(ReplaceStatementsIfNeeded(node.Statements)) - End Function - - Public Overrides Function VisitSingleLineIfStatement(node As SingleLineIfStatementSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitSingleLineIfStatement(node) - End If - - Return SyntaxFactory.SingleLineIfStatement(node.IfKeyword, - node.Condition, - node.ThenKeyword, - VisitList(ReplaceStatementsIfNeeded(node.Statements, colon:=True)), - node.ElseClause) - - End Function - - Public Overrides Function VisitSingleLineElseClause(node As SingleLineElseClauseSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitSingleLineElseClause(node) - End If - - Return SyntaxFactory.SingleLineElseClause(node.ElseKeyword, VisitList(ReplaceStatementsIfNeeded(node.Statements, colon:=True))) - End Function - - Public Overrides Function VisitMultiLineIfBlock(node As MultiLineIfBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitMultiLineIfBlock(node) - End If - - Return node.WithIfStatement(ReplaceStatementIfNeeded(node.IfStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitElseBlock(node As ElseBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitElseBlock(node) - End If - - Return node.WithElseStatement(ReplaceStatementIfNeeded(node.ElseStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitTryBlock(node As TryBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitTryBlock(node) - End If - - Return node.WithTryStatement(ReplaceStatementIfNeeded(node.TryStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitCatchBlock(node As CatchBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitCatchBlock(node) - End If - - Return node.WithCatchStatement(ReplaceStatementIfNeeded(node.CatchStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitFinallyBlock(node As FinallyBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitFinallyBlock(node) - End If - - Return node.WithFinallyStatement(ReplaceStatementIfNeeded(node.FinallyStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitSelectBlock(node As SelectBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitSelectBlock(node) - End If - - Return node.WithSelectStatement(ReplaceStatementIfNeeded(node.SelectStatement)). - WithCaseBlocks(VisitList(node.CaseBlocks)). - WithEndSelectStatement(ReplaceStatementIfNeeded(node.EndSelectStatement)) - End Function - - Public Overrides Function VisitCaseBlock(node As CaseBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitCaseBlock(node) - End If - - Return node.WithCaseStatement(ReplaceStatementIfNeeded(node.CaseStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) - End Function - - Public Overrides Function VisitDoLoopBlock(node As DoLoopBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitDoLoopBlock(node) - End If - - Return node.WithDoStatement(ReplaceStatementIfNeeded(node.DoStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))). - WithLoopStatement(ReplaceStatementIfNeeded(node.LoopStatement)) - End Function - - Public Overrides Function VisitForBlock(node As ForBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitForBlock(node) - End If - - Return node.WithForStatement(ReplaceStatementIfNeeded(node.ForStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))). - WithNextStatement(ReplaceStatementIfNeeded(node.NextStatement)) - End Function - - Public Overrides Function VisitForEachBlock(node As ForEachBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitForEachBlock(node) - End If - - Return node.WithForEachStatement(ReplaceStatementIfNeeded(node.ForEachStatement)). - WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))). - WithNextStatement(ReplaceStatementIfNeeded(node.NextStatement)) - End Function - - Public Overrides Function VisitSingleLineLambdaExpression(node As SingleLineLambdaExpressionSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitSingleLineLambdaExpression(node) - End If - - Dim body = SyntaxFactory.SingletonList(DirectCast(node.Body, StatementSyntax)) - Return node.WithBody(VisitList(ReplaceStatementsIfNeeded(body, colon:=True)).First()). - WithSubOrFunctionHeader(ReplaceStatementIfNeeded(node.SubOrFunctionHeader)) - End Function - - Public Overrides Function VisitMultiLineLambdaExpression(node As MultiLineLambdaExpressionSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - Return MyBase.VisitMultiLineLambdaExpression(node) - End If - - Return node.WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))). - WithSubOrFunctionHeader(ReplaceStatementIfNeeded(node.SubOrFunctionHeader)) - End Function - - Private Function ReplaceStatementIfNeeded(Of T As StatementSyntax)(statement As T) As T - Contract.ThrowIfNull(statement) - - ' if all three same - If (statement IsNot _firstStatementOrFieldToReplace) OrElse (Me._firstStatementOrFieldToReplace IsNot Me._lastStatementOrFieldToReplace) Then - Return statement - End If - - Contract.ThrowIfFalse(Me._statementsOrFieldToInsert.Count() = 1) - Return CType(Me._statementsOrFieldToInsert.Single(), T) - End Function - - Private Function ReplaceStatementsIfNeeded(statements As SyntaxList(Of StatementSyntax), Optional colon As Boolean = False) As SyntaxList(Of StatementSyntax) - Dim newStatements = New List(Of StatementSyntax)(statements) - Dim firstStatementIndex = newStatements.FindIndex(Function(s) s Is Me._firstStatementOrFieldToReplace) - - ' looks like statements belong to parent's Begin statement. there is nothing we need to do here. - If firstStatementIndex < 0 Then - Contract.ThrowIfFalse(Me._firstStatementOrFieldToReplace Is Me._lastStatementOrFieldToReplace) - Return statements - End If - - Dim lastStatementIndex = newStatements.FindIndex(Function(s) s Is Me._lastStatementOrFieldToReplace) - Contract.ThrowIfFalse(lastStatementIndex >= 0) - - Contract.ThrowIfFalse(firstStatementIndex <= lastStatementIndex) - - ' okay, this visit contains the statement - - ' remove statement that must be removed - statements = statements.RemoveRange(firstStatementIndex, lastStatementIndex - firstStatementIndex + 1) - - ' insert new statements - Return statements.InsertRange(firstStatementIndex, Join(Me._statementsOrFieldToInsert, colon).ToArray()) - End Function - - Private Shared Function Join(statements As IEnumerable(Of StatementSyntax), colon As Boolean) As IEnumerable(Of StatementSyntax) - If Not colon Then - Return statements - End If - - Dim removeEndOfLine = Function(t As SyntaxTrivia) Not t.IsElastic() AndAlso t.Kind <> SyntaxKind.EndOfLineTrivia - - Dim i = 0 - Dim count = statements.Count() - Dim trivia = SyntaxFactory.ColonTrivia(SyntaxFacts.GetText(SyntaxKind.ColonTrivia)) - Dim newStatements = New List(Of StatementSyntax) - For Each statement In statements - statement = statement.WithLeadingTrivia(statement.GetLeadingTrivia().Where(removeEndOfLine)) + If variableDeclarators.Count = 0 AndAlso + triviaList.Any(Function(t) t.Kind <> SyntaxKind.WhitespaceTrivia AndAlso t.Kind <> SyntaxKind.EndOfLineTrivia) Then + ' well, there are trivia associated with the node. + ' we can't just delete the node since then, we will lose + ' the trivia. unfortunately, it is not easy to attach the trivia + ' to next token. for now, create an empty statement and associate the + ' trivia to the statement + + ' TODO : think about a way to trivia attached to next token + Return SyntaxFactory.EmptyStatement(SyntaxFactory.Token(SyntaxKind.EmptyToken).WithLeadingTrivia(SyntaxFactory.TriviaList(triviaList))) + End If + + ' return survived var decls + If variableDeclarators.Count > 0 Then + Return SyntaxFactory.LocalDeclarationStatement( + node.Modifiers, + SyntaxFactory.SeparatedList(variableDeclarators)).WithPrependedLeadingTrivia(triviaList) + End If + + Return node.WithAdditionalAnnotations(s_removeAnnotation) + End Function + + Public Overrides Function VisitMethodBlock(node As MethodBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the block + Return MyBase.VisitMethodBlock(node) + End If + + Return node.WithSubOrFunctionStatement(ReplaceStatementIfNeeded(node.SubOrFunctionStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitConstructorBlock(node As ConstructorBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the block + Return MyBase.VisitConstructorBlock(node) + End If + + Return node.WithSubNewStatement(ReplaceStatementIfNeeded(node.SubNewStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitOperatorBlock(node As OperatorBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the block + Return MyBase.VisitOperatorBlock(node) + End If + + Return node.WithOperatorStatement(ReplaceStatementIfNeeded(node.OperatorStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitAccessorBlock(node As AccessorBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the block + Return MyBase.VisitAccessorBlock(node) + End If + + Return node.WithAccessorStatement(ReplaceStatementIfNeeded(node.AccessorStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitWhileBlock(node As WhileBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the switch section + Return MyBase.VisitWhileBlock(node) + End If + + Return node.WithWhileStatement(ReplaceStatementIfNeeded(node.WhileStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitUsingBlock(node As UsingBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitUsingBlock(node) + End If + + Return node.WithUsingStatement(ReplaceStatementIfNeeded(node.UsingStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitSyncLockBlock(node As SyncLockBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitSyncLockBlock(node) + End If + + Return node.WithSyncLockStatement(ReplaceStatementIfNeeded(node.SyncLockStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitWithBlock(node As WithBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitWithBlock(node) + End If + + Return node.WithWithStatement(ReplaceStatementIfNeeded(node.WithStatement)). + WithStatements(ReplaceStatementsIfNeeded(node.Statements)) + End Function + + Public Overrides Function VisitSingleLineIfStatement(node As SingleLineIfStatementSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitSingleLineIfStatement(node) + End If + + Return SyntaxFactory.SingleLineIfStatement(node.IfKeyword, + node.Condition, + node.ThenKeyword, + VisitList(ReplaceStatementsIfNeeded(node.Statements, colon:=True)), + node.ElseClause) + + End Function + + Public Overrides Function VisitSingleLineElseClause(node As SingleLineElseClauseSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitSingleLineElseClause(node) + End If + + Return SyntaxFactory.SingleLineElseClause(node.ElseKeyword, VisitList(ReplaceStatementsIfNeeded(node.Statements, colon:=True))) + End Function + + Public Overrides Function VisitMultiLineIfBlock(node As MultiLineIfBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitMultiLineIfBlock(node) + End If + + Return node.WithIfStatement(ReplaceStatementIfNeeded(node.IfStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitElseBlock(node As ElseBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitElseBlock(node) + End If + + Return node.WithElseStatement(ReplaceStatementIfNeeded(node.ElseStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitTryBlock(node As TryBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitTryBlock(node) + End If + + Return node.WithTryStatement(ReplaceStatementIfNeeded(node.TryStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitCatchBlock(node As CatchBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitCatchBlock(node) + End If + + Return node.WithCatchStatement(ReplaceStatementIfNeeded(node.CatchStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitFinallyBlock(node As FinallyBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitFinallyBlock(node) + End If + + Return node.WithFinallyStatement(ReplaceStatementIfNeeded(node.FinallyStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function + + Public Overrides Function VisitSelectBlock(node As SelectBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitSelectBlock(node) + End If + + Return node.WithSelectStatement(ReplaceStatementIfNeeded(node.SelectStatement)). + WithCaseBlocks(VisitList(node.CaseBlocks)). + WithEndSelectStatement(ReplaceStatementIfNeeded(node.EndSelectStatement)) + End Function + + Public Overrides Function VisitCaseBlock(node As CaseBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitCaseBlock(node) + End If - If i < count - 1 Then - statement = statement.WithTrailingTrivia(statement.GetTrailingTrivia().Where(removeEndOfLine).Concat(trivia)) - End If + Return node.WithCaseStatement(ReplaceStatementIfNeeded(node.CaseStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))) + End Function - newStatements.Add(statement) - i += 1 - Next + Public Overrides Function VisitDoLoopBlock(node As DoLoopBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitDoLoopBlock(node) + End If + + Return node.WithDoStatement(ReplaceStatementIfNeeded(node.DoStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))). + WithLoopStatement(ReplaceStatementIfNeeded(node.LoopStatement)) + End Function - Return newStatements - End Function - - Public Overrides Function VisitModuleBlock(ByVal node As ModuleBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the block - Return MyBase.VisitModuleBlock(node) - End If + Public Overrides Function VisitForBlock(node As ForBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitForBlock(node) + End If - Return node.WithMembers(VisitList(ReplaceStatementsIfNeeded(node.Members))) - End Function + Return node.WithForStatement(ReplaceStatementIfNeeded(node.ForStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))). + WithNextStatement(ReplaceStatementIfNeeded(node.NextStatement)) + End Function - Public Overrides Function VisitClassBlock(ByVal node As ClassBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the block - Return MyBase.VisitClassBlock(node) - End If + Public Overrides Function VisitForEachBlock(node As ForEachBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitForEachBlock(node) + End If + + Return node.WithForEachStatement(ReplaceStatementIfNeeded(node.ForEachStatement)). + WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))). + WithNextStatement(ReplaceStatementIfNeeded(node.NextStatement)) + End Function + + Public Overrides Function VisitSingleLineLambdaExpression(node As SingleLineLambdaExpressionSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitSingleLineLambdaExpression(node) + End If - Return node.WithMembers(VisitList(ReplaceStatementsIfNeeded(node.Members))) - End Function + Dim body = SyntaxFactory.SingletonList(DirectCast(node.Body, StatementSyntax)) + Return node.WithBody(VisitList(ReplaceStatementsIfNeeded(body, colon:=True)).First()). + WithSubOrFunctionHeader(ReplaceStatementIfNeeded(node.SubOrFunctionHeader)) + End Function + + Public Overrides Function VisitMultiLineLambdaExpression(node As MultiLineLambdaExpressionSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + Return MyBase.VisitMultiLineLambdaExpression(node) + End If + + Return node.WithStatements(VisitList(ReplaceStatementsIfNeeded(node.Statements))). + WithSubOrFunctionHeader(ReplaceStatementIfNeeded(node.SubOrFunctionHeader)) + End Function + + Private Function ReplaceStatementIfNeeded(Of T As StatementSyntax)(statement As T) As T + Contract.ThrowIfNull(statement) + + ' if all three same + If (statement IsNot _firstStatementOrFieldToReplace) OrElse (Me._firstStatementOrFieldToReplace IsNot Me._lastStatementOrFieldToReplace) Then + Return statement + End If - Public Overrides Function VisitStructureBlock(ByVal node As StructureBlockSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the block - Return MyBase.VisitStructureBlock(node) - End If + Contract.ThrowIfFalse(Me._statementsOrFieldToInsert.Count() = 1) + Return CType(Me._statementsOrFieldToInsert.Single(), T) + End Function - Return node.WithMembers(VisitList(ReplaceStatementsIfNeeded(node.Members))) - End Function + Private Function ReplaceStatementsIfNeeded(statements As SyntaxList(Of StatementSyntax), Optional colon As Boolean = False) As SyntaxList(Of StatementSyntax) + Dim newStatements = New List(Of StatementSyntax)(statements) + Dim firstStatementIndex = newStatements.FindIndex(Function(s) s Is Me._firstStatementOrFieldToReplace) - Public Overrides Function VisitCompilationUnit(node As CompilationUnitSyntax) As SyntaxNode - If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then - ' make sure we visit nodes under the block - Return MyBase.VisitCompilationUnit(node) - End If + ' looks like statements belong to parent's Begin statement. there is nothing we need to do here. + If firstStatementIndex < 0 Then + Contract.ThrowIfFalse(Me._firstStatementOrFieldToReplace Is Me._lastStatementOrFieldToReplace) + Return statements + End If + + Dim lastStatementIndex = newStatements.FindIndex(Function(s) s Is Me._lastStatementOrFieldToReplace) + Contract.ThrowIfFalse(lastStatementIndex >= 0) + + Contract.ThrowIfFalse(firstStatementIndex <= lastStatementIndex) + + ' okay, this visit contains the statement + + ' remove statement that must be removed + statements = statements.RemoveRange(firstStatementIndex, lastStatementIndex - firstStatementIndex + 1) + + ' insert new statements + Return statements.InsertRange(firstStatementIndex, Join(Me._statementsOrFieldToInsert, colon).ToArray()) + End Function + + Private Shared Function Join(statements As IEnumerable(Of StatementSyntax), colon As Boolean) As IEnumerable(Of StatementSyntax) + If Not colon Then + Return statements + End If + + Dim removeEndOfLine = Function(t As SyntaxTrivia) Not t.IsElastic() AndAlso t.Kind <> SyntaxKind.EndOfLineTrivia + + Dim i = 0 + Dim count = statements.Count() + Dim trivia = SyntaxFactory.ColonTrivia(SyntaxFacts.GetText(SyntaxKind.ColonTrivia)) + + Dim newStatements = New List(Of StatementSyntax) + For Each statement In statements + statement = statement.WithLeadingTrivia(statement.GetLeadingTrivia().Where(removeEndOfLine)) + + If i < count - 1 Then + statement = statement.WithTrailingTrivia(statement.GetTrailingTrivia().Where(removeEndOfLine).Concat(trivia)) + End If + + newStatements.Add(statement) + i += 1 + Next + + Return newStatements + End Function + + Public Overrides Function VisitModuleBlock(ByVal node As ModuleBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the block + Return MyBase.VisitModuleBlock(node) + End If + + Return node.WithMembers(VisitList(ReplaceStatementsIfNeeded(node.Members))) + End Function + + Public Overrides Function VisitClassBlock(ByVal node As ClassBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the block + Return MyBase.VisitClassBlock(node) + End If + + Return node.WithMembers(VisitList(ReplaceStatementsIfNeeded(node.Members))) + End Function + + Public Overrides Function VisitStructureBlock(ByVal node As StructureBlockSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the block + Return MyBase.VisitStructureBlock(node) + End If + + Return node.WithMembers(VisitList(ReplaceStatementsIfNeeded(node.Members))) + End Function + + Public Overrides Function VisitCompilationUnit(node As CompilationUnitSyntax) As SyntaxNode + If node IsNot Me.ContainerOfStatementsOrFieldToReplace Then + ' make sure we visit nodes under the block + Return MyBase.VisitCompilationUnit(node) + End If - Return node.WithMembers(VisitList(ReplaceStatementsIfNeeded(node.Members))) - End Function + Return node.WithMembers(VisitList(ReplaceStatementsIfNeeded(node.Members))) + End Function + End Class End Class End Class End Class diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.ExpressionCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.ExpressionCodeGenerator.vb index e124b3c8a728f..88dcfce67759c 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.ExpressionCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.ExpressionCodeGenerator.vb @@ -10,128 +10,130 @@ Imports Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Partial Private Class VisualBasicCodeGenerator - Private Class ExpressionCodeGenerator - Inherits VisualBasicCodeGenerator - - Public Sub New( - selectionResult As VisualBasicSelectionResult, - analyzerResult As AnalyzerResult, - options As ExtractMethodGenerationOptions) - MyBase.New(selectionResult, analyzerResult, options) - End Sub - - Public Shared Function IsExtractMethodOnExpression(code As VisualBasicSelectionResult) As Boolean - Return code.SelectionInExpression - End Function - - Protected Overrides Function CreateMethodName() As SyntaxToken - Dim methodName = "NewMethod" - Dim containingScope = Me.SelectionResult.GetContainingScope() - - methodName = GetMethodNameBasedOnExpression(methodName, containingScope) - - Dim semanticModel = SemanticDocument.SemanticModel - Dim nameGenerator = New UniqueNameGenerator(semanticModel) - Return SyntaxFactory.Identifier( - nameGenerator.CreateUniqueMethodName(containingScope, methodName)) - End Function - - Private Shared Function GetMethodNameBasedOnExpression(methodName As String, expression As SyntaxNode) As String - If expression.IsParentKind(SyntaxKind.EqualsValue) AndAlso - expression.Parent.IsParentKind(SyntaxKind.VariableDeclarator) Then - - Dim varDecl = DirectCast(expression.Parent.Parent, VariableDeclaratorSyntax) - If varDecl.Names.Count <> 1 Then - Return methodName + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Partial Private Class VisualBasicCodeGenerator + Private Class ExpressionCodeGenerator + Inherits VisualBasicCodeGenerator + + Public Sub New( + selectionResult As VisualBasicSelectionResult, + analyzerResult As AnalyzerResult, + options As ExtractMethodGenerationOptions) + MyBase.New(selectionResult, analyzerResult, options) + End Sub + + Public Shared Function IsExtractMethodOnExpression(code As VisualBasicSelectionResult) As Boolean + Return code.IsExtractMethodOnExpression + End Function + + Protected Overrides Function CreateMethodName() As SyntaxToken + Dim methodName = "NewMethod" + Dim containingScope = Me.SelectionResult.GetContainingScope() + + methodName = GetMethodNameBasedOnExpression(methodName, containingScope) + + Dim semanticModel = SemanticDocument.SemanticModel + Dim nameGenerator = New UniqueNameGenerator(semanticModel) + Return SyntaxFactory.Identifier( + nameGenerator.CreateUniqueMethodName(containingScope, methodName)) + End Function + + Private Shared Function GetMethodNameBasedOnExpression(methodName As String, expression As SyntaxNode) As String + If expression.IsParentKind(SyntaxKind.EqualsValue) AndAlso + expression.Parent.IsParentKind(SyntaxKind.VariableDeclarator) Then + + Dim varDecl = DirectCast(expression.Parent.Parent, VariableDeclaratorSyntax) + If varDecl.Names.Count <> 1 Then + Return methodName + End If + + Dim identifierNode = varDecl.Names(0) + If identifierNode Is Nothing Then + Return methodName + End If + + Dim name = identifierNode.Identifier.ValueText + Return If(name IsNot Nothing AndAlso name.Length > 0, MakeMethodName("Get", name, camelCase:=False), methodName) End If - Dim identifierNode = varDecl.Names(0) - If identifierNode Is Nothing Then - Return methodName + If TypeOf expression Is MemberAccessExpressionSyntax Then + expression = CType(expression, MemberAccessExpressionSyntax).Name End If - Dim name = identifierNode.Identifier.ValueText - Return If(name IsNot Nothing AndAlso name.Length > 0, MakeMethodName("Get", name, camelCase:=False), methodName) - End If - - If TypeOf expression Is MemberAccessExpressionSyntax Then - expression = CType(expression, MemberAccessExpressionSyntax).Name - End If - - If TypeOf expression Is NameSyntax Then - Dim lastDottedName = CType(expression, NameSyntax).GetLastDottedName() - Dim plainName = CType(lastDottedName, SimpleNameSyntax).Identifier.ValueText - Return If(plainName IsNot Nothing AndAlso plainName.Length > 0, MakeMethodName("Get", plainName, camelCase:=False), methodName) - End If - - Return methodName - End Function - - Protected Overrides Function GetInitialStatementsForMethodDefinitions() As ImmutableArray(Of StatementSyntax) - Contract.ThrowIfFalse(IsExtractMethodOnExpression(Me.SelectionResult)) - - Dim expression = DirectCast(Me.SelectionResult.GetContainingScope(), ExpressionSyntax) - - Dim statement As StatementSyntax - If Me.AnalyzerResult.HasReturnType Then - statement = SyntaxFactory.ReturnStatement(expression:=expression) - Else - ' we have expression for void method (Sub). make the expression as call - ' statement if possible we can create call statement only from invocation - ' and member access expression. otherwise, it is not a valid expression. - ' return error code - If expression.Kind <> SyntaxKind.InvocationExpression AndAlso - expression.Kind <> SyntaxKind.SimpleMemberAccessExpression Then - Return ImmutableArray(Of StatementSyntax).Empty + If TypeOf expression Is NameSyntax Then + Dim lastDottedName = CType(expression, NameSyntax).GetLastDottedName() + Dim plainName = CType(lastDottedName, SimpleNameSyntax).Identifier.ValueText + Return If(plainName IsNot Nothing AndAlso plainName.Length > 0, MakeMethodName("Get", plainName, camelCase:=False), methodName) End If - statement = SyntaxFactory.ExpressionStatement(expression:=expression) - End If + Return methodName + End Function - Return ImmutableArray.Create(statement) - End Function + Protected Overrides Function GetInitialStatementsForMethodDefinitions() As ImmutableArray(Of StatementSyntax) + Contract.ThrowIfFalse(IsExtractMethodOnExpression(Me.SelectionResult)) - Protected Overrides Function GetFirstStatementOrInitializerSelectedAtCallSite() As StatementSyntax - Return Me.SelectionResult.GetContainingScopeOf(Of StatementSyntax)() - End Function + Dim expression = DirectCast(Me.SelectionResult.GetContainingScope(), ExpressionSyntax) - Protected Overrides Function GetLastStatementOrInitializerSelectedAtCallSite() As StatementSyntax - Return GetFirstStatementOrInitializerSelectedAtCallSite() - End Function + Dim statement As StatementSyntax + If Me.AnalyzerResult.HasReturnType Then + statement = SyntaxFactory.ReturnStatement(expression:=expression) + Else + ' we have expression for void method (Sub). make the expression as call + ' statement if possible we can create call statement only from invocation + ' and member access expression. otherwise, it is not a valid expression. + ' return error code + If expression.Kind <> SyntaxKind.InvocationExpression AndAlso + expression.Kind <> SyntaxKind.SimpleMemberAccessExpression Then + Return ImmutableArray(Of StatementSyntax).Empty + End If - Protected Overrides Async Function GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken As CancellationToken) As Task(Of StatementSyntax) - Dim enclosingStatement = GetFirstStatementOrInitializerSelectedAtCallSite() - Dim callSignature = CreateCallSignature().WithAdditionalAnnotations(CallSiteAnnotation) - - Dim sourceNode = Me.SelectionResult.GetContainingScope() - Contract.ThrowIfTrue( - sourceNode.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) AndAlso - DirectCast(sourceNode.Parent, MemberAccessExpressionSyntax).Name Is sourceNode, - "invalid scope. scope is not an expression") - - ' To lower the chances that replacing sourceNode with callSignature will break the user's - ' code, we make the enclosing statement semantically explicit. This ends up being a little - ' bit more work because we need to annotate the sourceNode so that we can get back to it - ' after rewriting the enclosing statement. - Dim sourceNodeAnnotation = New SyntaxAnnotation() - Dim enclosingStatementAnnotation = New SyntaxAnnotation() - Dim newEnclosingStatement = enclosingStatement _ - .ReplaceNode(sourceNode, sourceNode.WithAdditionalAnnotations(sourceNodeAnnotation)) _ - .WithAdditionalAnnotations(enclosingStatementAnnotation) - - Dim updatedDocument = Await Me.SemanticDocument.Document.ReplaceNodeAsync(enclosingStatement, newEnclosingStatement, cancellationToken).ConfigureAwait(False) - Dim updatedRoot = Await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) - - newEnclosingStatement = DirectCast(updatedRoot.GetAnnotatedNodesAndTokens(enclosingStatementAnnotation).Single().AsNode(), StatementSyntax) - - ' because of the complexification we cannot guarantee that there is only one annotation. - ' however complexification of names is prepended, so the last annotation should be the original one. - sourceNode = updatedRoot.GetAnnotatedNodesAndTokens(sourceNodeAnnotation).Last().AsNode() + statement = SyntaxFactory.ExpressionStatement(expression:=expression) + End If - Return newEnclosingStatement.ReplaceNode(sourceNode, callSignature) - End Function + Return ImmutableArray.Create(statement) + End Function + + Protected Overrides Function GetFirstStatementOrInitializerSelectedAtCallSite() As StatementSyntax + Return Me.SelectionResult.GetContainingScopeOf(Of StatementSyntax)() + End Function + + Protected Overrides Function GetLastStatementOrInitializerSelectedAtCallSite() As StatementSyntax + Return GetFirstStatementOrInitializerSelectedAtCallSite() + End Function + + Protected Overrides Async Function GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken As CancellationToken) As Task(Of StatementSyntax) + Dim enclosingStatement = GetFirstStatementOrInitializerSelectedAtCallSite() + Dim callSignature = CreateCallSignature().WithAdditionalAnnotations(CallSiteAnnotation) + + Dim sourceNode = Me.SelectionResult.GetContainingScope() + Contract.ThrowIfTrue( + sourceNode.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) AndAlso + DirectCast(sourceNode.Parent, MemberAccessExpressionSyntax).Name Is sourceNode, + "invalid scope. scope is not an expression") + + ' To lower the chances that replacing sourceNode with callSignature will break the user's + ' code, we make the enclosing statement semantically explicit. This ends up being a little + ' bit more work because we need to annotate the sourceNode so that we can get back to it + ' after rewriting the enclosing statement. + Dim sourceNodeAnnotation = New SyntaxAnnotation() + Dim enclosingStatementAnnotation = New SyntaxAnnotation() + Dim newEnclosingStatement = enclosingStatement _ + .ReplaceNode(sourceNode, sourceNode.WithAdditionalAnnotations(sourceNodeAnnotation)) _ + .WithAdditionalAnnotations(enclosingStatementAnnotation) + + Dim updatedDocument = Await Me.SemanticDocument.Document.ReplaceNodeAsync(enclosingStatement, newEnclosingStatement, cancellationToken).ConfigureAwait(False) + Dim updatedRoot = Await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + + newEnclosingStatement = DirectCast(updatedRoot.GetAnnotatedNodesAndTokens(enclosingStatementAnnotation).Single().AsNode(), StatementSyntax) + + ' because of the complexification we cannot guarantee that there is only one annotation. + ' however complexification of names is prepended, so the last annotation should be the original one. + sourceNode = updatedRoot.GetAnnotatedNodesAndTokens(sourceNodeAnnotation).Last().AsNode() + + Return newEnclosingStatement.ReplaceNode(sourceNode, callSignature) + End Function + End Class End Class End Class End Class diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.MultipleStatementsCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.MultipleStatementsCodeGenerator.vb index 4f7bfeb528b63..3e547950896a6 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.MultipleStatementsCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.MultipleStatementsCodeGenerator.vb @@ -9,56 +9,58 @@ Imports Microsoft.CodeAnalysis.ExtractMethod Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Partial Private Class VisualBasicCodeGenerator - Private Class MultipleStatementsCodeGenerator - Inherits VisualBasicCodeGenerator + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Partial Private Class VisualBasicCodeGenerator + Private Class MultipleStatementsCodeGenerator + Inherits VisualBasicCodeGenerator - Public Sub New( - selectionResult As VisualBasicSelectionResult, - analyzerResult As AnalyzerResult, - options As ExtractMethodGenerationOptions) - MyBase.New(selectionResult, analyzerResult, options) - End Sub + Public Sub New( + selectionResult As VisualBasicSelectionResult, + analyzerResult As AnalyzerResult, + options As ExtractMethodGenerationOptions) + MyBase.New(selectionResult, analyzerResult, options) + End Sub - Protected Overrides Function CreateMethodName() As SyntaxToken - ' change this to more smarter one. - Dim semanticModel = SemanticDocument.SemanticModel - Dim nameGenerator = New UniqueNameGenerator(semanticModel) - Dim containingScope = Me.SelectionResult.GetContainingScope() - Return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(containingScope, "NewMethod")) - End Function + Protected Overrides Function CreateMethodName() As SyntaxToken + ' change this to more smarter one. + Dim semanticModel = SemanticDocument.SemanticModel + Dim nameGenerator = New UniqueNameGenerator(semanticModel) + Dim containingScope = Me.SelectionResult.GetContainingScope() + Return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(containingScope, "NewMethod")) + End Function - Protected Overrides Function GetInitialStatementsForMethodDefinitions() As ImmutableArray(Of StatementSyntax) - Dim firstStatementUnderContainer = Me.SelectionResult.GetFirstStatementUnderContainer() - Dim lastStatementUnderContainer = Me.SelectionResult.GetLastStatementUnderContainer() + Protected Overrides Function GetInitialStatementsForMethodDefinitions() As ImmutableArray(Of StatementSyntax) + Dim firstStatementUnderContainer = Me.SelectionResult.GetFirstStatementUnderContainer() + Dim lastStatementUnderContainer = Me.SelectionResult.GetLastStatementUnderContainer() - Dim statements = firstStatementUnderContainer.Parent.GetStatements() + Dim statements = firstStatementUnderContainer.Parent.GetStatements() - Dim firstStatementIndex = statements.IndexOf(firstStatementUnderContainer) - Contract.ThrowIfFalse(firstStatementIndex >= 0) + Dim firstStatementIndex = statements.IndexOf(firstStatementUnderContainer) + Contract.ThrowIfFalse(firstStatementIndex >= 0) - Dim lastStatementIndex = statements.IndexOf(lastStatementUnderContainer) - Contract.ThrowIfFalse(lastStatementIndex >= 0) + Dim lastStatementIndex = statements.IndexOf(lastStatementUnderContainer) + Contract.ThrowIfFalse(lastStatementIndex >= 0) - Dim nodes = statements. - Skip(firstStatementIndex). - Take(lastStatementIndex - firstStatementIndex + 1) + Dim nodes = statements. + Skip(firstStatementIndex). + Take(lastStatementIndex - firstStatementIndex + 1) - Return nodes.ToImmutableArray() - End Function + Return nodes.ToImmutableArray() + End Function - Protected Overrides Function GetFirstStatementOrInitializerSelectedAtCallSite() As StatementSyntax - Return Me.SelectionResult.GetFirstStatementUnderContainer() - End Function + Protected Overrides Function GetFirstStatementOrInitializerSelectedAtCallSite() As StatementSyntax + Return Me.SelectionResult.GetFirstStatementUnderContainer() + End Function - Protected Overrides Function GetLastStatementOrInitializerSelectedAtCallSite() As StatementSyntax - Return Me.SelectionResult.GetLastStatementUnderContainer() - End Function + Protected Overrides Function GetLastStatementOrInitializerSelectedAtCallSite() As StatementSyntax + Return Me.SelectionResult.GetLastStatementUnderContainer() + End Function - Protected Overrides Function GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken As CancellationToken) As Task(Of StatementSyntax) - Return Task.FromResult(GetStatementContainingInvocationToExtractedMethodWorker().WithAdditionalAnnotations(CallSiteAnnotation)) - End Function + Protected Overrides Function GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken As CancellationToken) As Task(Of StatementSyntax) + Return Task.FromResult(GetStatementContainingInvocationToExtractedMethodWorker().WithAdditionalAnnotations(CallSiteAnnotation)) + End Function + End Class End Class End Class End Class diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.SingleStatementCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.SingleStatementCodeGenerator.vb index f722f6ae15f9c..f0f031e1802f3 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.SingleStatementCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.SingleStatementCodeGenerator.vb @@ -9,46 +9,48 @@ Imports Microsoft.CodeAnalysis.ExtractMethod Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Partial Private Class VisualBasicCodeGenerator - Private Class SingleStatementCodeGenerator - Inherits VisualBasicCodeGenerator - - Public Sub New( - selectionResult As VisualBasicSelectionResult, - analyzerResult As AnalyzerResult, - options As ExtractMethodGenerationOptions) - MyBase.New(selectionResult, analyzerResult, options) - End Sub - - Protected Overrides Function CreateMethodName() As SyntaxToken - ' change this to more smarter one. - Dim semanticModel = CType(SemanticDocument.SemanticModel, SemanticModel) - Dim nameGenerator = New UniqueNameGenerator(semanticModel) - Dim containingScope = Me.SelectionResult.GetContainingScope() - Return SyntaxFactory.Identifier( - nameGenerator.CreateUniqueMethodName(containingScope, "NewMethod")) - End Function - - Protected Overrides Function GetInitialStatementsForMethodDefinitions() As ImmutableArray(Of StatementSyntax) - Contract.ThrowIfFalse(Me.SelectionResult.IsExtractMethodOnSingleStatement()) - - Return ImmutableArray.Create(Of StatementSyntax)(Me.SelectionResult.GetFirstStatement()) - End Function - - Protected Overrides Function GetFirstStatementOrInitializerSelectedAtCallSite() As StatementSyntax - Return Me.SelectionResult.GetFirstStatement() - End Function - - Protected Overrides Function GetLastStatementOrInitializerSelectedAtCallSite() As StatementSyntax - ' it is a single statement case. either first statement is same as last statement or - ' last statement belongs (embedded statement) to the first statement. - Return Me.SelectionResult.GetFirstStatement() - End Function - - Protected Overrides Function GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken As CancellationToken) As Task(Of StatementSyntax) - Return Task.FromResult(GetStatementContainingInvocationToExtractedMethodWorker().WithAdditionalAnnotations(CallSiteAnnotation)) - End Function + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Partial Private Class VisualBasicCodeGenerator + Private Class SingleStatementCodeGenerator + Inherits VisualBasicCodeGenerator + + Public Sub New( + selectionResult As VisualBasicSelectionResult, + analyzerResult As AnalyzerResult, + options As ExtractMethodGenerationOptions) + MyBase.New(selectionResult, analyzerResult, options) + End Sub + + Protected Overrides Function CreateMethodName() As SyntaxToken + ' change this to more smarter one. + Dim semanticModel = CType(SemanticDocument.SemanticModel, SemanticModel) + Dim nameGenerator = New UniqueNameGenerator(semanticModel) + Dim containingScope = Me.SelectionResult.GetContainingScope() + Return SyntaxFactory.Identifier( + nameGenerator.CreateUniqueMethodName(containingScope, "NewMethod")) + End Function + + Protected Overrides Function GetInitialStatementsForMethodDefinitions() As ImmutableArray(Of StatementSyntax) + Contract.ThrowIfFalse(Me.SelectionResult.IsExtractMethodOnSingleStatement()) + + Return ImmutableArray.Create(Of StatementSyntax)(Me.SelectionResult.GetFirstStatement()) + End Function + + Protected Overrides Function GetFirstStatementOrInitializerSelectedAtCallSite() As StatementSyntax + Return Me.SelectionResult.GetFirstStatement() + End Function + + Protected Overrides Function GetLastStatementOrInitializerSelectedAtCallSite() As StatementSyntax + ' it is a single statement case. either first statement is same as last statement or + ' last statement belongs (embedded statement) to the first statement. + Return Me.SelectionResult.GetFirstStatement() + End Function + + Protected Overrides Function GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(cancellationToken As CancellationToken) As Task(Of StatementSyntax) + Return Task.FromResult(GetStatementContainingInvocationToExtractedMethodWorker().WithAdditionalAnnotations(CallSiteAnnotation)) + End Function + End Class End Class End Class End Class diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb index 0bc3c2131711d..1a1b72875174c 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb @@ -15,443 +15,445 @@ Imports Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Partial Private MustInherit Class VisualBasicCodeGenerator - Inherits CodeGenerator(Of StatementSyntax, StatementSyntax, VisualBasicCodeGenerationOptions) - - Private ReadOnly _methodName As SyntaxToken - - Public Shared Async Function GenerateResultAsync( - insertionPoint As InsertionPoint, - selectionResult As VisualBasicSelectionResult, - analyzerResult As AnalyzerResult, - options As ExtractMethodGenerationOptions, - cancellationToken As CancellationToken) As Task(Of GeneratedCode) - Dim generator = Create(selectionResult, analyzerResult, options) - Return Await generator.GenerateAsync(insertionPoint, cancellationToken).ConfigureAwait(False) - End Function - - Public Shared Function Create( - selectionResult As VisualBasicSelectionResult, - analyzerResult As AnalyzerResult, - options As ExtractMethodGenerationOptions) As VisualBasicCodeGenerator - If selectionResult.SelectionInExpression Then - Return New ExpressionCodeGenerator(selectionResult, analyzerResult, options) - End If - - If selectionResult.IsExtractMethodOnSingleStatement() Then - Return New SingleStatementCodeGenerator(selectionResult, analyzerResult, options) - End If - - If selectionResult.IsExtractMethodOnMultipleStatements() Then - Return New MultipleStatementsCodeGenerator(selectionResult, analyzerResult, options) - End If - - Throw ExceptionUtilities.UnexpectedValue(selectionResult) - End Function - - Protected Sub New( - selectionResult As VisualBasicSelectionResult, - analyzerResult As AnalyzerResult, - options As ExtractMethodGenerationOptions) - MyBase.New(selectionResult, analyzerResult, options, localFunction:=False) - Contract.ThrowIfFalse(Me.SemanticDocument Is selectionResult.SemanticDocument) - - Me._methodName = CreateMethodName().WithAdditionalAnnotations(MethodNameAnnotation) - End Sub - - Protected Overrides Function UpdateMethodAfterGenerationAsync(originalDocument As SemanticDocument, methodSymbol As IMethodSymbol, cancellationToken As CancellationToken) As Task(Of SemanticDocument) - Return Task.FromResult(originalDocument) - End Function - - Protected Overrides Function ShouldLocalFunctionCaptureParameter(node As SyntaxNode) As Boolean - Return False - End Function - - Protected Overrides Function GenerateMethodDefinition(insertionPointNode As SyntaxNode, cancellationToken As CancellationToken) As IMethodSymbol - Dim statements = CreateMethodBody(insertionPointNode, cancellationToken) - - Dim methodSymbol = CodeGenerationSymbolFactory.CreateMethodSymbol( - attributes:=ImmutableArray(Of AttributeData).Empty, - accessibility:=Accessibility.Private, - modifiers:=CreateMethodModifiers(), - returnType:=Me.AnalyzerResult.ReturnType, - refKind:=RefKind.None, - explicitInterfaceImplementations:=Nothing, - name:=_methodName.ToString(), - typeParameters:=CreateMethodTypeParameters(), - parameters:=CreateMethodParameters(), - statements:=statements.CastArray(Of SyntaxNode)) - - Return Me.MethodDefinitionAnnotation.AddAnnotationToSymbol( - Formatter.Annotation.AddAnnotationToSymbol(methodSymbol)) - End Function - - Protected Overrides Async Function GenerateBodyForCallSiteContainerAsync( - insertionPointNode As SyntaxNode, - container As SyntaxNode, - cancellationToken As CancellationToken) As Task(Of SyntaxNode) - Dim variableMapToRemove = CreateVariableDeclarationToRemoveMap(AnalyzerResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken), cancellationToken) - Dim firstStatementToRemove = GetFirstStatementOrInitializerSelectedAtCallSite() - Dim lastStatementToRemove = GetLastStatementOrInitializerSelectedAtCallSite() - - Contract.ThrowIfFalse(firstStatementToRemove.Parent Is lastStatementToRemove.Parent) - - Dim statementsToInsert = Await CreateStatementsToInsertAtCallSiteAsync( - insertionPointNode, cancellationToken).ConfigureAwait(False) - - Dim callSiteGenerator = New CallSiteContainerRewriter( - container, - variableMapToRemove, - firstStatementToRemove, - lastStatementToRemove, - statementsToInsert) - - Return container.CopyAnnotationsTo(callSiteGenerator.Generate()).WithAdditionalAnnotations(Formatter.Annotation) - End Function - - Private Async Function CreateStatementsToInsertAtCallSiteAsync( - insertionPointNode As SyntaxNode, cancellationToken As CancellationToken) As Task(Of IEnumerable(Of StatementSyntax)) - Dim semanticModel = SemanticDocument.SemanticModel - Dim postProcessor = New PostProcessor(semanticModel, insertionPointNode.SpanStart) - - Dim statements = AddSplitOrMoveDeclarationOutStatementsToCallSite(cancellationToken) - statements = postProcessor.MergeDeclarationStatements(statements) - statements = AddAssignmentStatementToCallSite(statements, cancellationToken) - statements = Await AddInvocationAtCallSiteAsync(statements, cancellationToken).ConfigureAwait(False) - statements = AddReturnIfUnreachable(statements) - - Return statements - End Function - - Private Function CreateMethodNameForInvocation() As SimpleNameSyntax - If AnalyzerResult.MethodTypeParametersInDeclaration.Count = 0 Then - Return SyntaxFactory.IdentifierName(_methodName) - End If - - Return SyntaxFactory.GenericName(_methodName, SyntaxFactory.TypeArgumentList(arguments:=CreateMethodCallTypeVariables())) - End Function - - Private Function CreateMethodCallTypeVariables() As SeparatedSyntaxList(Of TypeSyntax) - Contract.ThrowIfTrue(AnalyzerResult.MethodTypeParametersInDeclaration.Count = 0) - - ' propagate any type variable used in extracted code - Dim typeVariables = (From methodTypeParameter In AnalyzerResult.MethodTypeParametersInDeclaration - Select SyntaxFactory.ParseTypeName(methodTypeParameter.Name)).ToList() - - Return SyntaxFactory.SeparatedList(typeVariables) - End Function - - Protected Overrides Function GetCallSiteContainerFromOutermostMoveInVariable(cancellationToken As CancellationToken) As SyntaxNode - Dim outmostVariable = GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken) - If outmostVariable Is Nothing Then - Return Nothing - End If - - Dim idToken = outmostVariable.GetIdentifierTokenAtDeclaration(SemanticDocument) - Dim declStatement = idToken.GetAncestor(Of LocalDeclarationStatementSyntax)() - - Contract.ThrowIfNull(declStatement) - Contract.ThrowIfFalse(declStatement.Parent.IsStatementContainerNode()) - - Return declStatement.Parent - End Function + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Partial Private MustInherit Class VisualBasicCodeGenerator + Inherits CodeGenerator(Of StatementSyntax, StatementSyntax, VisualBasicCodeGenerationOptions) + + Private ReadOnly _methodName As SyntaxToken + + Public Shared Async Function GenerateResultAsync( + insertionPoint As InsertionPoint, + selectionResult As VisualBasicSelectionResult, + analyzerResult As AnalyzerResult, + options As ExtractMethodGenerationOptions, + cancellationToken As CancellationToken) As Task(Of GeneratedCode) + Dim generator = Create(selectionResult, analyzerResult, options) + Return Await generator.GenerateAsync(insertionPoint, cancellationToken).ConfigureAwait(False) + End Function + + Public Shared Function Create( + selectionResult As VisualBasicSelectionResult, + analyzerResult As AnalyzerResult, + options As ExtractMethodGenerationOptions) As VisualBasicCodeGenerator + If selectionResult.IsExtractMethodOnExpression Then + Return New ExpressionCodeGenerator(selectionResult, analyzerResult, options) + End If - Private Function CreateMethodModifiers() As DeclarationModifiers - Dim isShared = False - - If Not Me.AnalyzerResult.UseInstanceMember AndAlso - Not Me.SelectionResult.IsUnderModuleBlock() AndAlso - Not Me.SelectionResult.ContainsInstanceExpression() Then - isShared = True - End If - - Dim isAsync = Me.SelectionResult.CreateAsyncMethod() - - Return New DeclarationModifiers(isStatic:=isShared, isAsync:=isAsync) - End Function - - Public Overrides Function GetNewMethodStatements( - insertionPointNode As SyntaxNode, cancellationToken As CancellationToken) As OperationStatus(Of ImmutableArray(Of SyntaxNode)) - Dim statements = CreateMethodBody(insertionPointNode, cancellationToken) - Dim status = CheckActiveStatements(statements) - Return status.With(statements.CastArray(Of SyntaxNode)) - End Function + If selectionResult.IsExtractMethodOnSingleStatement() Then + Return New SingleStatementCodeGenerator(selectionResult, analyzerResult, options) + End If - Private Function CreateMethodBody(insertionPointNode As SyntaxNode, cancellationToken As CancellationToken) As ImmutableArray(Of StatementSyntax) - Dim statements = GetInitialStatementsForMethodDefinitions() - statements = SplitOrMoveDeclarationIntoMethodDefinition(insertionPointNode, statements, cancellationToken) - statements = MoveDeclarationOutFromMethodDefinition(statements, cancellationToken) + If selectionResult.IsExtractMethodOnMultipleStatements() Then + Return New MultipleStatementsCodeGenerator(selectionResult, analyzerResult, options) + End If - Dim emptyStatements = ImmutableArray(Of StatementSyntax).Empty - Dim returnStatements = AppendReturnStatementIfNeeded(emptyStatements) + Throw ExceptionUtilities.UnexpectedValue(selectionResult) + End Function - statements = statements.Concat(returnStatements) + Protected Sub New( + selectionResult As VisualBasicSelectionResult, + analyzerResult As AnalyzerResult, + options As ExtractMethodGenerationOptions) + MyBase.New(selectionResult, analyzerResult, options, localFunction:=False) + Contract.ThrowIfFalse(Me.SemanticDocument Is selectionResult.SemanticDocument) - Dim semanticModel = SemanticDocument.SemanticModel + Me._methodName = CreateMethodName().WithAdditionalAnnotations(MethodNameAnnotation) + End Sub - statements = PostProcessor.RemoveDeclarationAssignmentPattern(statements) - statements = PostProcessor.RemoveInitializedDeclarationAndReturnPattern(statements) + Protected Overrides Function UpdateMethodAfterGenerationAsync(originalDocument As SemanticDocument, methodSymbol As IMethodSymbol, cancellationToken As CancellationToken) As Task(Of SemanticDocument) + Return Task.FromResult(originalDocument) + End Function - Return statements - End Function + Protected Overrides Function ShouldLocalFunctionCaptureParameter(node As SyntaxNode) As Boolean + Return False + End Function + + Protected Overrides Function GenerateMethodDefinition(insertionPointNode As SyntaxNode, cancellationToken As CancellationToken) As IMethodSymbol + Dim statements = CreateMethodBody(insertionPointNode, cancellationToken) + + Dim methodSymbol = CodeGenerationSymbolFactory.CreateMethodSymbol( + attributes:=ImmutableArray(Of AttributeData).Empty, + accessibility:=Accessibility.Private, + modifiers:=CreateMethodModifiers(), + returnType:=Me.AnalyzerResult.ReturnType, + refKind:=RefKind.None, + explicitInterfaceImplementations:=Nothing, + name:=_methodName.ToString(), + typeParameters:=CreateMethodTypeParameters(), + parameters:=CreateMethodParameters(), + statements:=statements.CastArray(Of SyntaxNode)) + + Return Me.MethodDefinitionAnnotation.AddAnnotationToSymbol( + Formatter.Annotation.AddAnnotationToSymbol(methodSymbol)) + End Function + + Protected Overrides Async Function GenerateBodyForCallSiteContainerAsync( + insertionPointNode As SyntaxNode, + container As SyntaxNode, + cancellationToken As CancellationToken) As Task(Of SyntaxNode) + Dim variableMapToRemove = CreateVariableDeclarationToRemoveMap(AnalyzerResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken), cancellationToken) + Dim firstStatementToRemove = GetFirstStatementOrInitializerSelectedAtCallSite() + Dim lastStatementToRemove = GetLastStatementOrInitializerSelectedAtCallSite() + + Contract.ThrowIfFalse(firstStatementToRemove.Parent Is lastStatementToRemove.Parent) + + Dim statementsToInsert = Await CreateStatementsToInsertAtCallSiteAsync( + insertionPointNode, cancellationToken).ConfigureAwait(False) + + Dim callSiteGenerator = New CallSiteContainerRewriter( + container, + variableMapToRemove, + firstStatementToRemove, + lastStatementToRemove, + statementsToInsert) + + Return container.CopyAnnotationsTo(callSiteGenerator.Generate()).WithAdditionalAnnotations(Formatter.Annotation) + End Function + + Private Async Function CreateStatementsToInsertAtCallSiteAsync( + insertionPointNode As SyntaxNode, cancellationToken As CancellationToken) As Task(Of IEnumerable(Of StatementSyntax)) + Dim semanticModel = SemanticDocument.SemanticModel + Dim postProcessor = New PostProcessor(semanticModel, insertionPointNode.SpanStart) + + Dim statements = AddSplitOrMoveDeclarationOutStatementsToCallSite(cancellationToken) + statements = postProcessor.MergeDeclarationStatements(statements) + statements = AddAssignmentStatementToCallSite(statements, cancellationToken) + statements = Await AddInvocationAtCallSiteAsync(statements, cancellationToken).ConfigureAwait(False) + statements = AddReturnIfUnreachable(statements) + + Return statements + End Function + + Private Function CreateMethodNameForInvocation() As SimpleNameSyntax + If AnalyzerResult.MethodTypeParametersInDeclaration.Count = 0 Then + Return SyntaxFactory.IdentifierName(_methodName) + End If - Private Shared Function CheckActiveStatements(statements As ImmutableArray(Of StatementSyntax)) As OperationStatus - Dim count = statements.Count() - If count = 0 Then - Return OperationStatus.NoActiveStatement - End If + Return SyntaxFactory.GenericName(_methodName, SyntaxFactory.TypeArgumentList(arguments:=CreateMethodCallTypeVariables())) + End Function + + Private Function CreateMethodCallTypeVariables() As SeparatedSyntaxList(Of TypeSyntax) + Contract.ThrowIfTrue(AnalyzerResult.MethodTypeParametersInDeclaration.Count = 0) + + ' propagate any type variable used in extracted code + Dim typeVariables = (From methodTypeParameter In AnalyzerResult.MethodTypeParametersInDeclaration + Select SyntaxFactory.ParseTypeName(methodTypeParameter.Name)).ToList() + + Return SyntaxFactory.SeparatedList(typeVariables) + End Function + + Protected Overrides Function GetCallSiteContainerFromOutermostMoveInVariable(cancellationToken As CancellationToken) As SyntaxNode + Dim outmostVariable = GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken) + If outmostVariable Is Nothing Then + Return Nothing + End If + + Dim idToken = outmostVariable.GetIdentifierTokenAtDeclaration(SemanticDocument) + Dim declStatement = idToken.GetAncestor(Of LocalDeclarationStatementSyntax)() + + Contract.ThrowIfNull(declStatement) + Contract.ThrowIfFalse(declStatement.Parent.IsStatementContainerNode()) + + Return declStatement.Parent + End Function - If count = 1 Then - Dim returnStatement = TryCast(statements(0), ReturnStatementSyntax) - If returnStatement IsNot Nothing AndAlso returnStatement.Expression Is Nothing Then + Private Function CreateMethodModifiers() As DeclarationModifiers + Dim isShared = False + + If Not Me.AnalyzerResult.UseInstanceMember AndAlso + Not Me.SelectionResult.IsUnderModuleBlock() AndAlso + Not Me.SelectionResult.ContainsInstanceExpression() Then + isShared = True + End If + + Dim isAsync = Me.SelectionResult.CreateAsyncMethod() + + Return New DeclarationModifiers(isStatic:=isShared, isAsync:=isAsync) + End Function + + Public Overrides Function GetNewMethodStatements( + insertionPointNode As SyntaxNode, cancellationToken As CancellationToken) As OperationStatus(Of ImmutableArray(Of SyntaxNode)) + Dim statements = CreateMethodBody(insertionPointNode, cancellationToken) + Dim status = CheckActiveStatements(statements) + Return status.With(statements.CastArray(Of SyntaxNode)) + End Function + + Private Function CreateMethodBody(insertionPointNode As SyntaxNode, cancellationToken As CancellationToken) As ImmutableArray(Of StatementSyntax) + Dim statements = GetInitialStatementsForMethodDefinitions() + statements = SplitOrMoveDeclarationIntoMethodDefinition(insertionPointNode, statements, cancellationToken) + statements = MoveDeclarationOutFromMethodDefinition(statements, cancellationToken) + + Dim emptyStatements = ImmutableArray(Of StatementSyntax).Empty + Dim returnStatements = AppendReturnStatementIfNeeded(emptyStatements) + + statements = statements.Concat(returnStatements) + + Dim semanticModel = SemanticDocument.SemanticModel + + statements = PostProcessor.RemoveDeclarationAssignmentPattern(statements) + statements = PostProcessor.RemoveInitializedDeclarationAndReturnPattern(statements) + + Return statements + End Function + + Private Shared Function CheckActiveStatements(statements As ImmutableArray(Of StatementSyntax)) As OperationStatus + Dim count = statements.Count() + If count = 0 Then Return OperationStatus.NoActiveStatement End If - End If - For Each statement In statements - Dim localDeclStatement = TryCast(statement, LocalDeclarationStatementSyntax) - If localDeclStatement Is Nothing Then - 'found one - Return OperationStatus.SucceededStatus + If count = 1 Then + Dim returnStatement = TryCast(statements(0), ReturnStatementSyntax) + If returnStatement IsNot Nothing AndAlso returnStatement.Expression Is Nothing Then + Return OperationStatus.NoActiveStatement + End If End If - For Each variableDecl In localDeclStatement.Declarators - If variableDecl.Initializer IsNot Nothing Then - 'found one - Return OperationStatus.SucceededStatus - ElseIf TypeOf variableDecl.AsClause Is AsNewClauseSyntax Then + For Each statement In statements + Dim localDeclStatement = TryCast(statement, LocalDeclarationStatementSyntax) + If localDeclStatement Is Nothing Then 'found one Return OperationStatus.SucceededStatus End If + + For Each variableDecl In localDeclStatement.Declarators + If variableDecl.Initializer IsNot Nothing Then + 'found one + Return OperationStatus.SucceededStatus + ElseIf TypeOf variableDecl.AsClause Is AsNewClauseSyntax Then + 'found one + Return OperationStatus.SucceededStatus + End If + Next Next - Next - Return OperationStatus.NoActiveStatement - End Function + Return OperationStatus.NoActiveStatement + End Function - Private Function MoveDeclarationOutFromMethodDefinition(statements As ImmutableArray(Of StatementSyntax), cancellationToken As CancellationToken) As ImmutableArray(Of StatementSyntax) - Dim variableToRemoveMap = CreateVariableDeclarationToRemoveMap( - Me.AnalyzerResult.GetVariablesToMoveOutToCallSiteOrDelete(cancellationToken), cancellationToken) + Private Function MoveDeclarationOutFromMethodDefinition(statements As ImmutableArray(Of StatementSyntax), cancellationToken As CancellationToken) As ImmutableArray(Of StatementSyntax) + Dim variableToRemoveMap = CreateVariableDeclarationToRemoveMap( + Me.AnalyzerResult.GetVariablesToMoveOutToCallSiteOrDelete(cancellationToken), cancellationToken) - Dim declarationStatements = New List(Of StatementSyntax)() + Dim declarationStatements = New List(Of StatementSyntax)() - For Each statement In statements + For Each statement In statements - Dim declarationStatement = TryCast(statement, LocalDeclarationStatementSyntax) - If declarationStatement Is Nothing Then - ' if given statement is not decl statement, do nothing. - declarationStatements.Add(statement) - Continue For - End If + Dim declarationStatement = TryCast(statement, LocalDeclarationStatementSyntax) + If declarationStatement Is Nothing Then + ' if given statement is not decl statement, do nothing. + declarationStatements.Add(statement) + Continue For + End If - Dim expressionStatements = New List(Of StatementSyntax)() - Dim variableDeclarators = New List(Of VariableDeclaratorSyntax)() - Dim triviaList = New List(Of SyntaxTrivia)() + Dim expressionStatements = New List(Of StatementSyntax)() + Dim variableDeclarators = New List(Of VariableDeclaratorSyntax)() + Dim triviaList = New List(Of SyntaxTrivia)() - If Not variableToRemoveMap.ProcessLocalDeclarationStatement(declarationStatement, expressionStatements, variableDeclarators, triviaList) Then - declarationStatements.Add(statement) - Continue For - End If + If Not variableToRemoveMap.ProcessLocalDeclarationStatement(declarationStatement, expressionStatements, variableDeclarators, triviaList) Then + declarationStatements.Add(statement) + Continue For + End If - If variableDeclarators.Count = 0 AndAlso - triviaList.Any(Function(t) t.Kind <> SyntaxKind.WhitespaceTrivia AndAlso t.Kind <> SyntaxKind.EndOfLineTrivia) Then - ' well, there are trivia associated with the node. - ' we can't just delete the node since then, we will lose - ' the trivia. unfortunately, it is not easy to attach the trivia - ' to next token. for now, create an empty statement and associate the - ' trivia to the statement + If variableDeclarators.Count = 0 AndAlso + triviaList.Any(Function(t) t.Kind <> SyntaxKind.WhitespaceTrivia AndAlso t.Kind <> SyntaxKind.EndOfLineTrivia) Then + ' well, there are trivia associated with the node. + ' we can't just delete the node since then, we will lose + ' the trivia. unfortunately, it is not easy to attach the trivia + ' to next token. for now, create an empty statement and associate the + ' trivia to the statement - ' TODO : think about a way to trivia attached to next token - Dim emptyStatement = SyntaxFactory.EmptyStatement(SyntaxFactory.Token(SyntaxKind.EmptyToken).WithLeadingTrivia(SyntaxFactory.TriviaList(triviaList))) - declarationStatements.Add(emptyStatement) + ' TODO : think about a way to trivia attached to next token + Dim emptyStatement = SyntaxFactory.EmptyStatement(SyntaxFactory.Token(SyntaxKind.EmptyToken).WithLeadingTrivia(SyntaxFactory.TriviaList(triviaList))) + declarationStatements.Add(emptyStatement) - triviaList.Clear() - End If + triviaList.Clear() + End If - ' return survived var decls - If variableDeclarators.Count > 0 Then - Dim localStatement = - SyntaxFactory.LocalDeclarationStatement( - declarationStatement.Modifiers, - SyntaxFactory.SeparatedList(variableDeclarators)).WithPrependedLeadingTrivia(triviaList) + ' return survived var decls + If variableDeclarators.Count > 0 Then + Dim localStatement = + SyntaxFactory.LocalDeclarationStatement( + declarationStatement.Modifiers, + SyntaxFactory.SeparatedList(variableDeclarators)).WithPrependedLeadingTrivia(triviaList) - declarationStatements.Add(localStatement) - triviaList.Clear() - End If + declarationStatements.Add(localStatement) + triviaList.Clear() + End If - ' return any expression statement if there was any - For Each expressionStatement In expressionStatements - declarationStatements.Add(expressionStatement) - Next expressionStatement - Next + ' return any expression statement if there was any + For Each expressionStatement In expressionStatements + declarationStatements.Add(expressionStatement) + Next expressionStatement + Next - Return declarationStatements.ToImmutableArray() - End Function + Return declarationStatements.ToImmutableArray() + End Function - Private Function SplitOrMoveDeclarationIntoMethodDefinition( - insertionPointNode As SyntaxNode, - statements As ImmutableArray(Of StatementSyntax), - cancellationToken As CancellationToken) As ImmutableArray(Of StatementSyntax) - Dim semanticModel = Me.SemanticDocument.SemanticModel - Dim postProcessor = New PostProcessor(semanticModel, insertionPointNode.SpanStart) + Private Function SplitOrMoveDeclarationIntoMethodDefinition( + insertionPointNode As SyntaxNode, + statements As ImmutableArray(Of StatementSyntax), + cancellationToken As CancellationToken) As ImmutableArray(Of StatementSyntax) + Dim semanticModel = Me.SemanticDocument.SemanticModel + Dim postProcessor = New PostProcessor(semanticModel, insertionPointNode.SpanStart) - Dim declStatements = CreateDeclarationStatements(AnalyzerResult.GetVariablesToSplitOrMoveIntoMethodDefinition(cancellationToken), cancellationToken) - declStatements = postProcessor.MergeDeclarationStatements(declStatements) + Dim declStatements = CreateDeclarationStatements(AnalyzerResult.GetVariablesToSplitOrMoveIntoMethodDefinition(cancellationToken), cancellationToken) + declStatements = postProcessor.MergeDeclarationStatements(declStatements) - Return declStatements.Concat(statements) - End Function + Return declStatements.Concat(statements) + End Function - Protected Overrides Function CreateIdentifier(name As String) As SyntaxToken - Return name.ToIdentifierToken() - End Function + Protected Overrides Function CreateIdentifier(name As String) As SyntaxToken + Return name.ToIdentifierToken() + End Function - Protected Overrides Function CreateReturnStatement(ParamArray identifierNames As String()) As StatementSyntax - Contract.ThrowIfTrue(identifierNames.Length > 1) + Protected Overrides Function CreateReturnStatement(ParamArray identifierNames As String()) As StatementSyntax + Contract.ThrowIfTrue(identifierNames.Length > 1) - If identifierNames.Length = 0 Then - Return SyntaxFactory.ReturnStatement() - End If + If identifierNames.Length = 0 Then + Return SyntaxFactory.ReturnStatement() + End If - Return SyntaxFactory.ReturnStatement(identifierNames(0).ToIdentifierName()) - End Function + Return SyntaxFactory.ReturnStatement(identifierNames(0).ToIdentifierName()) + End Function - Protected Overrides Function LastStatementOrHasReturnStatementInReturnableConstruct() As Boolean - Dim lastStatement = GetLastStatementOrInitializerSelectedAtCallSite() - Dim container = lastStatement.GetAncestorsOrThis(Of SyntaxNode).Where(Function(n) n.IsReturnableConstruct()).FirstOrDefault() - If container Is Nothing Then - ' case such as field initializer - Return False - End If + Protected Overrides Function LastStatementOrHasReturnStatementInReturnableConstruct() As Boolean + Dim lastStatement = GetLastStatementOrInitializerSelectedAtCallSite() + Dim container = lastStatement.GetAncestorsOrThis(Of SyntaxNode).Where(Function(n) n.IsReturnableConstruct()).FirstOrDefault() + If container Is Nothing Then + ' case such as field initializer + Return False + End If - Dim statements = container.GetStatements() - If statements.Count = 0 Then - ' such as expression lambda - Return False - End If - - If statements.Last() Is lastStatement Then - Return True - End If - - Dim index = statements.IndexOf(lastStatement) - Return statements(index + 1).IsKind(SyntaxKind.ReturnStatement, SyntaxKind.ExitSubStatement) - End Function - - Protected Overrides Function CreateCallSignature() As ExpressionSyntax - Dim methodName = CreateMethodNameForInvocation().WithAdditionalAnnotations(Simplifier.Annotation) - - Dim methodExpression = - If(Me.AnalyzerResult.UseInstanceMember AndAlso Me.ExtractMethodGenerationOptions.SimplifierOptions.QualifyMethodAccess.Value, - SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.MeExpression(), SyntaxFactory.Token(SyntaxKind.DotToken), methodName), - DirectCast(methodName, ExpressionSyntax)) - - Dim arguments = New List(Of ArgumentSyntax)() - For Each argument In AnalyzerResult.MethodParameters - arguments.Add(SyntaxFactory.SimpleArgument(GetIdentifierName(argument.Name))) - Next argument - - Dim invocation = SyntaxFactory.InvocationExpression( - methodExpression, SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(arguments))) - - If Me.SelectionResult.CreateAsyncMethod() Then - If Me.SelectionResult.ShouldCallConfigureAwaitFalse() Then - If AnalyzerResult.ReturnType.GetMembers().Any( - Function(x) - Dim method = TryCast(x, IMethodSymbol) - If method Is Nothing Then - Return False - End If + Dim statements = container.GetStatements() + If statements.Count = 0 Then + ' such as expression lambda + Return False + End If - If Not CaseInsensitiveComparison.Equals(method.Name, NameOf(Task.ConfigureAwait)) Then - Return False - End If + If statements.Last() Is lastStatement Then + Return True + End If - If method.Parameters.Length <> 1 Then - Return False + Dim index = statements.IndexOf(lastStatement) + Return statements(index + 1).IsKind(SyntaxKind.ReturnStatement, SyntaxKind.ExitSubStatement) + End Function + + Protected Overrides Function CreateCallSignature() As ExpressionSyntax + Dim methodName = CreateMethodNameForInvocation().WithAdditionalAnnotations(Simplifier.Annotation) + + Dim methodExpression = + If(Me.AnalyzerResult.UseInstanceMember AndAlso Me.ExtractMethodGenerationOptions.SimplifierOptions.QualifyMethodAccess.Value, + SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.MeExpression(), SyntaxFactory.Token(SyntaxKind.DotToken), methodName), + DirectCast(methodName, ExpressionSyntax)) + + Dim arguments = New List(Of ArgumentSyntax)() + For Each argument In AnalyzerResult.MethodParameters + arguments.Add(SyntaxFactory.SimpleArgument(GetIdentifierName(argument.Name))) + Next argument + + Dim invocation = SyntaxFactory.InvocationExpression( + methodExpression, SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(arguments))) + + If Me.SelectionResult.CreateAsyncMethod() Then + If Me.SelectionResult.ShouldCallConfigureAwaitFalse() Then + If AnalyzerResult.ReturnType.GetMembers().Any( + Function(x) + Dim method = TryCast(x, IMethodSymbol) + If method Is Nothing Then + Return False + End If + + If Not CaseInsensitiveComparison.Equals(method.Name, NameOf(Task.ConfigureAwait)) Then + Return False + End If + + If method.Parameters.Length <> 1 Then + Return False + End If + + Return method.Parameters(0).Type.SpecialType = SpecialType.System_Boolean + End Function) Then + + invocation = SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + invocation, + SyntaxFactory.Token(SyntaxKind.DotToken), + SyntaxFactory.IdentifierName(NameOf(Task.ConfigureAwait))), + SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(Of ArgumentSyntax)( + SyntaxFactory.SimpleArgument( + SyntaxFactory.LiteralExpression( + SyntaxKind.FalseLiteralExpression, + SyntaxFactory.Token(SyntaxKind.FalseKeyword)))))) End If - - Return method.Parameters(0).Type.SpecialType = SpecialType.System_Boolean - End Function) Then - - invocation = SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - invocation, - SyntaxFactory.Token(SyntaxKind.DotToken), - SyntaxFactory.IdentifierName(NameOf(Task.ConfigureAwait))), - SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(Of ArgumentSyntax)( - SyntaxFactory.SimpleArgument( - SyntaxFactory.LiteralExpression( - SyntaxKind.FalseLiteralExpression, - SyntaxFactory.Token(SyntaxKind.FalseKeyword)))))) End If + + Return SyntaxFactory.AwaitExpression(invocation) + End If + + Return invocation + End Function + + Private Shared Function GetIdentifierName(name As String) As ExpressionSyntax + Dim bracket = SyntaxFacts.MakeHalfWidthIdentifier(name.First) = "[" AndAlso SyntaxFacts.MakeHalfWidthIdentifier(name.Last) = "]" + If bracket Then + Dim unescaped = name.Substring(1, name.Length() - 2) + Return SyntaxFactory.IdentifierName(SyntaxFactory.BracketedIdentifier(unescaped)) End If - Return SyntaxFactory.AwaitExpression(invocation) - End If - - Return invocation - End Function - - Private Shared Function GetIdentifierName(name As String) As ExpressionSyntax - Dim bracket = SyntaxFacts.MakeHalfWidthIdentifier(name.First) = "[" AndAlso SyntaxFacts.MakeHalfWidthIdentifier(name.Last) = "]" - If bracket Then - Dim unescaped = name.Substring(1, name.Length() - 2) - Return SyntaxFactory.IdentifierName(SyntaxFactory.BracketedIdentifier(unescaped)) - End If - - Return SyntaxFactory.IdentifierName(name) - End Function - - Protected Overrides Function CreateAssignmentExpressionStatement( - variables As ImmutableArray(Of VariableInfo), - rvalue As ExpressionSyntax) As StatementSyntax - Contract.ThrowIfTrue(variables.Length <> 1) - Dim identifier = variables(0).Name.ToIdentifierToken() - Return identifier.CreateAssignmentExpressionStatementWithValue(rvalue) - End Function - - Protected Overrides Function CreateDeclarationStatement( - variables As ImmutableArray(Of VariableInfo), - initialValue As ExpressionSyntax, - cancellationToken As CancellationToken) As StatementSyntax - Contract.ThrowIfTrue(variables.Length <> 1) - - Dim variable = variables(0) - Dim shouldInitializeWithNothing = (variable.GetDeclarationBehavior(cancellationToken) = DeclarationBehavior.MoveOut OrElse variable.GetDeclarationBehavior(cancellationToken) = DeclarationBehavior.SplitOut) AndAlso - (variable.ParameterModifier = ParameterBehavior.Out) - - Dim initializer = If(initialValue, If(shouldInitializeWithNothing, SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword)), Nothing)) - - Dim variableType = variable.GetVariableType() - Dim typeNode = variableType.GenerateTypeSyntax() - - Dim names = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.ModifiedIdentifier(SyntaxFactory.Identifier(variable.Name))) - Dim modifiers = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.DimKeyword)) - Dim equalsValue = If(initializer Is Nothing, Nothing, SyntaxFactory.EqualsValue(value:=initializer)) - Dim declarators = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(names, SyntaxFactory.SimpleAsClause(type:=typeNode), equalsValue)) - - Return SyntaxFactory.LocalDeclarationStatement(modifiers, declarators) - End Function - - Protected Overrides Async Function CreateGeneratedCodeAsync(newDocument As SemanticDocument, cancellationToken As CancellationToken) As Task(Of GeneratedCode) - ' in hybrid code cases such as extract method, formatter will have some difficulties on where it breaks lines in two. - ' here, we explicitly insert newline at the end of auto generated method decl's begin statement so that anchor knows how to find out - ' indentation of inserted statements (from users code) with user code style preserved - Dim root = newDocument.Root - Dim methodDefinition = root.GetAnnotatedNodes(Of MethodBlockBaseSyntax)(Me.MethodDefinitionAnnotation).First() - Dim lastTokenOfBeginStatement = methodDefinition.BlockStatement.GetLastToken(includeZeroWidth:=True) - - Dim newMethodDefinition = methodDefinition.ReplaceToken( - lastTokenOfBeginStatement, - lastTokenOfBeginStatement.WithAppendedTrailingTrivia( - SpecializedCollections.SingletonEnumerable(SyntaxFactory.ElasticCarriageReturnLineFeed))) - - newDocument = Await newDocument.WithSyntaxRootAsync(root.ReplaceNode(methodDefinition, newMethodDefinition), cancellationToken).ConfigureAwait(False) - - Return Await MyBase.CreateGeneratedCodeAsync(newDocument, cancellationToken).ConfigureAwait(False) - End Function + Return SyntaxFactory.IdentifierName(name) + End Function + + Protected Overrides Function CreateAssignmentExpressionStatement( + variables As ImmutableArray(Of VariableInfo), + rvalue As ExpressionSyntax) As StatementSyntax + Contract.ThrowIfTrue(variables.Length <> 1) + Dim identifier = variables(0).Name.ToIdentifierToken() + Return identifier.CreateAssignmentExpressionStatementWithValue(rvalue) + End Function + + Protected Overrides Function CreateDeclarationStatement( + variables As ImmutableArray(Of VariableInfo), + initialValue As ExpressionSyntax, + cancellationToken As CancellationToken) As StatementSyntax + Contract.ThrowIfTrue(variables.Length <> 1) + + Dim variable = variables(0) + Dim shouldInitializeWithNothing = (variable.GetDeclarationBehavior(cancellationToken) = DeclarationBehavior.MoveOut OrElse variable.GetDeclarationBehavior(cancellationToken) = DeclarationBehavior.SplitOut) AndAlso + (variable.ParameterModifier = ParameterBehavior.Out) + + Dim initializer = If(initialValue, If(shouldInitializeWithNothing, SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword)), Nothing)) + + Dim variableType = variable.GetVariableType() + Dim typeNode = variableType.GenerateTypeSyntax() + + Dim names = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.ModifiedIdentifier(SyntaxFactory.Identifier(variable.Name))) + Dim modifiers = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.DimKeyword)) + Dim equalsValue = If(initializer Is Nothing, Nothing, SyntaxFactory.EqualsValue(value:=initializer)) + Dim declarators = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(names, SyntaxFactory.SimpleAsClause(type:=typeNode), equalsValue)) + + Return SyntaxFactory.LocalDeclarationStatement(modifiers, declarators) + End Function + + Protected Overrides Async Function CreateGeneratedCodeAsync(newDocument As SemanticDocument, cancellationToken As CancellationToken) As Task(Of GeneratedCode) + ' in hybrid code cases such as extract method, formatter will have some difficulties on where it breaks lines in two. + ' here, we explicitly insert newline at the end of auto generated method decl's begin statement so that anchor knows how to find out + ' indentation of inserted statements (from users code) with user code style preserved + Dim root = newDocument.Root + Dim methodDefinition = root.GetAnnotatedNodes(Of MethodBlockBaseSyntax)(Me.MethodDefinitionAnnotation).First() + Dim lastTokenOfBeginStatement = methodDefinition.BlockStatement.GetLastToken(includeZeroWidth:=True) + + Dim newMethodDefinition = methodDefinition.ReplaceToken( + lastTokenOfBeginStatement, + lastTokenOfBeginStatement.WithAppendedTrailingTrivia( + SpecializedCollections.SingletonEnumerable(SyntaxFactory.ElasticCarriageReturnLineFeed))) + + newDocument = Await newDocument.WithSyntaxRootAsync(root.ReplaceNode(methodDefinition, newMethodDefinition), cancellationToken).ConfigureAwait(False) + + Return Await MyBase.CreateGeneratedCodeAsync(newDocument, cancellationToken).ConfigureAwait(False) + End Function + End Class End Class End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.vb index 7e6eb16921431..72f9d59964c26 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.vb @@ -9,122 +9,124 @@ Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicMethodExtractor - Inherits MethodExtractor(Of VisualBasicSelectionResult, ExecutableStatementSyntax, ExpressionSyntax) + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicMethodExtractor + Inherits MethodExtractor - Public Sub New(result As VisualBasicSelectionResult, options As ExtractMethodGenerationOptions) - MyBase.New(result, options, localFunction:=False) - End Sub - - Protected Overrides Function CreateCodeGenerator(analyzerResult As AnalyzerResult) As CodeGenerator - Return VisualBasicCodeGenerator.Create(Me.OriginalSelectionResult, analyzerResult, Me.Options) - End Function - - Protected Overrides Function Analyze(selectionResult As VisualBasicSelectionResult, localFunction As Boolean, cancellationToken As CancellationToken) As AnalyzerResult - Return VisualBasicAnalyzer.AnalyzeResult(selectionResult, cancellationToken) - End Function + Public Sub New(result As VisualBasicSelectionResult, options As ExtractMethodGenerationOptions) + MyBase.New(result, options, localFunction:=False) + End Sub - Protected Overrides Function GetInsertionPointNode( - analyzerResult As AnalyzerResult, cancellationToken As CancellationToken) As SyntaxNode - Dim document = Me.OriginalSelectionResult.SemanticDocument - Dim originalSpanStart = OriginalSelectionResult.OriginalSpan.Start - Contract.ThrowIfFalse(originalSpanStart >= 0) + Protected Overrides Function CreateCodeGenerator(analyzerResult As AnalyzerResult) As CodeGenerator + Return VisualBasicCodeGenerator.Create(Me.OriginalSelectionResult, analyzerResult, Me.Options) + End Function - Dim root = document.Root - Dim basePosition = root.FindToken(originalSpanStart) + Protected Overrides Function Analyze(selectionResult As VisualBasicSelectionResult, localFunction As Boolean, cancellationToken As CancellationToken) As AnalyzerResult + Return VisualBasicAnalyzer.AnalyzeResult(selectionResult, cancellationToken) + End Function - Dim enclosingTopLevelNode As SyntaxNode = basePosition.GetAncestor(Of PropertyBlockSyntax)() - If enclosingTopLevelNode Is Nothing Then - enclosingTopLevelNode = basePosition.GetAncestor(Of EventBlockSyntax)() - End If + Protected Overrides Function GetInsertionPointNode( + analyzerResult As AnalyzerResult, cancellationToken As CancellationToken) As SyntaxNode + Dim document = Me.OriginalSelectionResult.SemanticDocument + Dim originalSpanStart = OriginalSelectionResult.OriginalSpan.Start + Contract.ThrowIfFalse(originalSpanStart >= 0) - If enclosingTopLevelNode Is Nothing Then - enclosingTopLevelNode = basePosition.GetAncestor(Of MethodBlockBaseSyntax)() - End If + Dim root = document.Root + Dim basePosition = root.FindToken(originalSpanStart) - If enclosingTopLevelNode Is Nothing Then - enclosingTopLevelNode = basePosition.GetAncestor(Of FieldDeclarationSyntax)() - End If + Dim enclosingTopLevelNode As SyntaxNode = basePosition.GetAncestor(Of PropertyBlockSyntax)() + If enclosingTopLevelNode Is Nothing Then + enclosingTopLevelNode = basePosition.GetAncestor(Of EventBlockSyntax)() + End If - If enclosingTopLevelNode Is Nothing Then - enclosingTopLevelNode = basePosition.GetAncestor(Of PropertyStatementSyntax)() - End If + If enclosingTopLevelNode Is Nothing Then + enclosingTopLevelNode = basePosition.GetAncestor(Of MethodBlockBaseSyntax)() + End If - Contract.ThrowIfNull(enclosingTopLevelNode) - Return enclosingTopLevelNode - End Function + If enclosingTopLevelNode Is Nothing Then + enclosingTopLevelNode = basePosition.GetAncestor(Of FieldDeclarationSyntax)() + End If - Protected Overrides Async Function PreserveTriviaAsync(selectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) As Task(Of TriviaResult) - Return Await VisualBasicTriviaResult.ProcessAsync(selectionResult, cancellationToken).ConfigureAwait(False) - End Function + If enclosingTopLevelNode Is Nothing Then + enclosingTopLevelNode = basePosition.GetAncestor(Of PropertyStatementSyntax)() + End If - Protected Overrides Function GenerateCodeAsync(insertionPoint As InsertionPoint, selectionResult As VisualBasicSelectionResult, analyzeResult As AnalyzerResult, options As ExtractMethodGenerationOptions, cancellationToken As CancellationToken) As Task(Of GeneratedCode) - Return VisualBasicCodeGenerator.GenerateResultAsync(insertionPoint, selectionResult, analyzeResult, options, cancellationToken) - End Function + Contract.ThrowIfNull(enclosingTopLevelNode) + Return enclosingTopLevelNode + End Function - Protected Overrides Function GetCustomFormattingRule(document As Document) As AbstractFormattingRule - Return FormattingRule.Instance - End Function + Protected Overrides Async Function PreserveTriviaAsync(selectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) As Task(Of TriviaResult) + Return Await VisualBasicTriviaResult.ProcessAsync(selectionResult, cancellationToken).ConfigureAwait(False) + End Function - Protected Overrides Function GetInvocationNameToken(methodNames As IEnumerable(Of SyntaxToken)) As SyntaxToken? - Return methodNames.FirstOrNull(Function(t) t.Parent.Kind <> SyntaxKind.SubStatement AndAlso t.Parent.Kind <> SyntaxKind.FunctionStatement) - End Function + Protected Overrides Function GenerateCodeAsync(insertionPoint As InsertionPoint, selectionResult As VisualBasicSelectionResult, analyzeResult As AnalyzerResult, options As ExtractMethodGenerationOptions, cancellationToken As CancellationToken) As Task(Of GeneratedCode) + Return VisualBasicCodeGenerator.GenerateResultAsync(insertionPoint, selectionResult, analyzeResult, options, cancellationToken) + End Function - Protected Overrides Function ParseTypeName(name As String) As SyntaxNode - Return SyntaxFactory.ParseTypeName(name) - End Function + Protected Overrides Function GetCustomFormattingRule(document As Document) As AbstractFormattingRule + Return FormattingRule.Instance + End Function - Private NotInheritable Class FormattingRule - Inherits CompatAbstractFormattingRule + Protected Overrides Function GetInvocationNameToken(methodNames As IEnumerable(Of SyntaxToken)) As SyntaxToken? + Return methodNames.FirstOrNull(Function(t) t.Parent.Kind <> SyntaxKind.SubStatement AndAlso t.Parent.Kind <> SyntaxKind.FunctionStatement) + End Function - Public Shared ReadOnly Instance As New FormattingRule() + Protected Overrides Function ParseTypeName(name As String) As SyntaxNode + Return SyntaxFactory.ParseTypeName(name) + End Function - Private Sub New() - End Sub + Private NotInheritable Class FormattingRule + Inherits CompatAbstractFormattingRule - Public Overrides Function GetAdjustNewLinesOperationSlow(ByRef previousToken As SyntaxToken, ByRef currentToken As SyntaxToken, ByRef nextOperation As NextGetAdjustNewLinesOperation) As AdjustNewLinesOperation - If Not previousToken.IsLastTokenOfStatement() Then - Return nextOperation.Invoke(previousToken, currentToken) - End If + Public Shared ReadOnly Instance As New FormattingRule() - ' between [generated code] and [existing code] - If Not CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(previousToken, currentToken) Then - Return nextOperation.Invoke(previousToken, currentToken) - End If + Private Sub New() + End Sub - ' make sure attribute and previous statement has at least 1 blank lines between them - If IsLessThanInAttribute(currentToken) Then - Return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.ForceLines) - End If + Public Overrides Function GetAdjustNewLinesOperationSlow(ByRef previousToken As SyntaxToken, ByRef currentToken As SyntaxToken, ByRef nextOperation As NextGetAdjustNewLinesOperation) As AdjustNewLinesOperation + If Not previousToken.IsLastTokenOfStatement() Then + Return nextOperation.Invoke(previousToken, currentToken) + End If - ' make sure previous statement and next type has at least 1 blank lines between them - If TypeOf currentToken.Parent Is TypeStatementSyntax AndAlso - currentToken.Parent.GetFirstToken(includeZeroWidth:=True) = currentToken Then - Return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.ForceLines) - End If + ' between [generated code] and [existing code] + If Not CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(previousToken, currentToken) Then + Return nextOperation.Invoke(previousToken, currentToken) + End If - Return nextOperation.Invoke(previousToken, currentToken) - End Function + ' make sure attribute and previous statement has at least 1 blank lines between them + If IsLessThanInAttribute(currentToken) Then + Return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.ForceLines) + End If - Private Shared Function IsLessThanInAttribute(token As SyntaxToken) As Boolean - ' < in attribute - If token.Kind = SyntaxKind.LessThanToken AndAlso - token.Parent.Kind = SyntaxKind.AttributeList AndAlso - DirectCast(token.Parent, AttributeListSyntax).LessThanToken.Equals(token) Then - Return True - End If + ' make sure previous statement and next type has at least 1 blank lines between them + If TypeOf currentToken.Parent Is TypeStatementSyntax AndAlso + currentToken.Parent.GetFirstToken(includeZeroWidth:=True) = currentToken Then + Return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.ForceLines) + End If - Return False + Return nextOperation.Invoke(previousToken, currentToken) + End Function + + Private Shared Function IsLessThanInAttribute(token As SyntaxToken) As Boolean + ' < in attribute + If token.Kind = SyntaxKind.LessThanToken AndAlso + token.Parent.Kind = SyntaxKind.AttributeList AndAlso + DirectCast(token.Parent, AttributeListSyntax).LessThanToken.Equals(token) Then + Return True + End If + + Return False + End Function + End Class + + Protected Overrides Function InsertNewLineBeforeLocalFunctionIfNecessaryAsync( + document As Document, + invocationNameToken? As SyntaxToken, + methodDefinition As SyntaxNode, + cancellationToken As CancellationToken) As Task(Of (document As Document, invocationNameToken As SyntaxToken?)) + ' VB doesn't need to do any correction, so we just return the values untouched + Return Task.FromResult((document, invocationNameToken)) End Function End Class - - Protected Overrides Function InsertNewLineBeforeLocalFunctionIfNecessaryAsync( - document As Document, - invocationNameToken? As SyntaxToken, - methodDefinition As SyntaxNode, - cancellationToken As CancellationToken) As Task(Of (document As Document, invocationNameToken As SyntaxToken?)) - ' VB doesn't need to do any correction, so we just return the values untouched - Return Task.FromResult((document, invocationNameToken)) - End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionResult.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionResult.vb index bccd7e1afb57d..1d146222b738f 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionResult.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionResult.vb @@ -6,7 +6,6 @@ Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.ExtractMethod Imports Microsoft.CodeAnalysis.LanguageService -Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService @@ -14,316 +13,305 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Friend Class VisualBasicSelectionResult - Inherits SelectionResult(Of ExecutableStatementSyntax) + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Friend NotInheritable Class VisualBasicSelectionResult + Inherits SelectionResult - Public Shared Async Function CreateResultAsync( - originalSpan As TextSpan, - finalSpan As TextSpan, - selectionInExpression As Boolean, + Public Shared Async Function CreateResultAsync( document As SemanticDocument, - firstToken As SyntaxToken, - lastToken As SyntaxToken, + selectionInfo As SelectionInfo, selectionChanged As Boolean, cancellationToken As CancellationToken) As Task(Of VisualBasicSelectionResult) - Contract.ThrowIfNull(document) - - Dim firstAnnotation = New SyntaxAnnotation() - Dim lastAnnotation = New SyntaxAnnotation() - - Dim root = document.Root - Dim newDocument = Await SemanticDocument.CreateAsync(document.Document.WithSyntaxRoot(AddAnnotations( - root, {(firstToken, firstAnnotation), (lastToken, lastAnnotation)})), cancellationToken).ConfigureAwait(False) - - Return New VisualBasicSelectionResult( - originalSpan, - finalSpan, - selectionInExpression, - newDocument, - firstAnnotation, - lastAnnotation, - selectionChanged) - End Function - - Private Sub New( - originalSpan As TextSpan, - finalSpan As TextSpan, - selectionInExpression As Boolean, - document As SemanticDocument, - firstTokenAnnotation As SyntaxAnnotation, - lastTokenAnnotation As SyntaxAnnotation, - selectionChanged As Boolean) - - MyBase.New( - originalSpan, - finalSpan, - selectionInExpression, - document, - firstTokenAnnotation, - lastTokenAnnotation, - selectionChanged) - End Sub - - Protected Overrides ReadOnly Property SyntaxFacts As ISyntaxFacts = VisualBasicSyntaxFacts.Instance - - Protected Overrides Function UnderAnonymousOrLocalMethod(token As SyntaxToken, firstToken As SyntaxToken, lastToken As SyntaxToken) As Boolean - Dim current = token.Parent - - While current IsNot Nothing - If TypeOf current Is DeclarationStatementSyntax OrElse - TypeOf current Is LambdaExpressionSyntax Then - Exit While + Contract.ThrowIfNull(document) + + Dim root = document.Root + Dim newDocument = Await SemanticDocument.CreateAsync(document.Document.WithSyntaxRoot(AddAnnotations( + root, {(selectionInfo.FirstTokenInFinalSpan, s_firstTokenAnnotation), (selectionInfo.LastTokenInFinalSpan, s_lastTokenAnnotation)})), cancellationToken).ConfigureAwait(False) + + Return New VisualBasicSelectionResult( + newDocument, + selectionInfo.GetSelectionType(), + selectionInfo.OriginalSpan, + selectionInfo.FinalSpan, + selectionChanged) + End Function + + Private Sub New( + document As SemanticDocument, + selectionType As SelectionType, + originalSpan As TextSpan, + finalSpan As TextSpan, + selectionChanged As Boolean) + + MyBase.New( + document, + selectionType, + originalSpan, + finalSpan, + selectionChanged) + End Sub + + Protected Overrides ReadOnly Property SyntaxFacts As ISyntaxFacts = VisualBasicSyntaxFacts.Instance + + Protected Overrides Function UnderAnonymousOrLocalMethod(token As SyntaxToken, firstToken As SyntaxToken, lastToken As SyntaxToken) As Boolean + Dim current = token.Parent + + While current IsNot Nothing + If TypeOf current Is DeclarationStatementSyntax OrElse + TypeOf current Is LambdaExpressionSyntax Then + Exit While + End If + + current = current.Parent + End While + + If current Is Nothing OrElse TypeOf current Is DeclarationStatementSyntax Then + Return False End If - current = current.Parent - End While + ' make sure selection contains the lambda + Return firstToken.SpanStart <= current.GetFirstToken().SpanStart AndAlso + current.GetLastToken().Span.End <= lastToken.Span.End + End Function + + Public Overrides Function GetOutermostCallSiteContainerToProcess(cancellationToken As CancellationToken) As SyntaxNode + If Me.IsExtractMethodOnExpression Then + Dim container = Me.InnermostStatementContainer() + + Contract.ThrowIfNull(container) + Contract.ThrowIfFalse(container.IsStatementContainerNode() OrElse + TypeOf container Is TypeBlockSyntax OrElse + TypeOf container Is CompilationUnitSyntax) + + Return container + ElseIf Me.IsExtractMethodOnSingleStatement() Then + Dim first = Me.GetFirstStatement() + Return first.Parent + ElseIf Me.IsExtractMethodOnMultipleStatements() Then + Return Me.GetFirstStatementUnderContainer().Parent + Else + Throw ExceptionUtilities.Unreachable() + End If + End Function - If current Is Nothing OrElse TypeOf current Is DeclarationStatementSyntax Then - Return False - End If - - ' make sure selection contains the lambda - Return firstToken.SpanStart <= current.GetFirstToken().SpanStart AndAlso - current.GetLastToken().Span.End <= lastToken.Span.End - End Function - - Public Overrides Function GetOutermostCallSiteContainerToProcess(cancellationToken As CancellationToken) As SyntaxNode - If Me.SelectionInExpression Then - Dim container = Me.InnermostStatementContainer() - - Contract.ThrowIfNull(container) - Contract.ThrowIfFalse(container.IsStatementContainerNode() OrElse - TypeOf container Is TypeBlockSyntax OrElse - TypeOf container Is CompilationUnitSyntax) - - Return container - ElseIf Me.IsExtractMethodOnSingleStatement() Then - Dim first = Me.GetFirstStatement() - Return first.Parent - ElseIf Me.IsExtractMethodOnMultipleStatements() Then - Return Me.GetFirstStatementUnderContainer().Parent - Else - Throw ExceptionUtilities.Unreachable() - End If - End Function - - Public Overrides Function ContainingScopeHasAsyncKeyword() As Boolean - If SelectionInExpression Then - Return False - End If + Public Overrides Function ContainingScopeHasAsyncKeyword() As Boolean + If IsExtractMethodOnExpression Then + Return False + End If - Dim node = Me.GetContainingScope() - If TypeOf node Is MethodBlockBaseSyntax Then - Dim methodBlock = DirectCast(node, MethodBlockBaseSyntax) - If methodBlock.BlockStatement IsNot Nothing Then - Return methodBlock.BlockStatement.Modifiers.Any(SyntaxKind.AsyncKeyword) + Dim node = Me.GetContainingScope() + If TypeOf node Is MethodBlockBaseSyntax Then + Dim methodBlock = DirectCast(node, MethodBlockBaseSyntax) + If methodBlock.BlockStatement IsNot Nothing Then + Return methodBlock.BlockStatement.Modifiers.Any(SyntaxKind.AsyncKeyword) + End If + + Return False + ElseIf TypeOf node Is LambdaExpressionSyntax Then + Dim lambda = DirectCast(node, LambdaExpressionSyntax) + If lambda.SubOrFunctionHeader IsNot Nothing Then + Return lambda.SubOrFunctionHeader.Modifiers.Any(SyntaxKind.AsyncKeyword) + End If End If Return False - ElseIf TypeOf node Is LambdaExpressionSyntax Then - Dim lambda = DirectCast(node, LambdaExpressionSyntax) - If lambda.SubOrFunctionHeader IsNot Nothing Then - Return lambda.SubOrFunctionHeader.Modifiers.Any(SyntaxKind.AsyncKeyword) - End If - End If + End Function - Return False - End Function + Public Overrides Function GetContainingScope() As SyntaxNode + Contract.ThrowIfNull(Me.SemanticDocument) - Public Overrides Function GetContainingScope() As SyntaxNode - Contract.ThrowIfNull(Me.SemanticDocument) + Dim first = GetFirstTokenInSelection() - Dim first = GetFirstTokenInSelection() + If IsExtractMethodOnExpression Then + Dim last = GetLastTokenInSelection() - If SelectionInExpression Then - Dim last = GetLastTokenInSelection() + Dim scope = first.GetCommonRoot(last).GetAncestorOrThis(Of ExpressionSyntax)() + Contract.ThrowIfNull(scope, "Should always find an expression given that SelectionInExpression was true") - Dim scope = first.GetCommonRoot(last).GetAncestorOrThis(Of ExpressionSyntax)() - Contract.ThrowIfNull(scope, "Should always find an expression given that SelectionInExpression was true") - - Return VisualBasicSyntaxFacts.Instance.GetRootStandaloneExpression(scope) - Else - ' it contains statements - Return first.GetAncestors(Of SyntaxNode).FirstOrDefault(Function(n) TypeOf n Is MethodBlockBaseSyntax OrElse TypeOf n Is LambdaExpressionSyntax) - End If - End Function - - Public Overrides Function GetReturnType() As (returnType As ITypeSymbol, returnsByRef As Boolean) - ' Todo: consider supporting byref return types in VB - Dim returnType = GetReturnTypeWorker() - Return (returnType, returnsByRef:=False) - End Function - - Private Function GetReturnTypeWorker() As ITypeSymbol - Dim node = Me.GetContainingScope() - Dim semanticModel = Me.SemanticDocument.SemanticModel - - ' special case for collection initializer and explicit cast - If node.IsExpressionInCast() Then - Dim castExpression = TryCast(node.Parent, CastExpressionSyntax) - If castExpression IsNot Nothing Then - Return semanticModel.GetTypeInfo(castExpression.Type).Type - End If - End If - - Dim expression As ExpressionSyntax - If TypeOf node Is CollectionInitializerSyntax Then - expression = node.GetUnparenthesizedExpression() - Return semanticModel.GetTypeInfo(expression).ConvertedType - End If - - Dim methodBlock = TryCast(node, MethodBlockBaseSyntax) - If methodBlock IsNot Nothing Then - Dim symbol = semanticModel.GetDeclaredSymbol(methodBlock.BlockStatement) - Dim propertySymbol = TryCast(symbol, IPropertySymbol) - If propertySymbol IsNot Nothing Then - Return propertySymbol.Type + Return VisualBasicSyntaxFacts.Instance.GetRootStandaloneExpression(scope) Else - Return DirectCast(symbol, IMethodSymbol).ReturnType + ' it contains statements + Return first.GetAncestors(Of SyntaxNode).FirstOrDefault(Function(n) TypeOf n Is MethodBlockBaseSyntax OrElse TypeOf n Is LambdaExpressionSyntax) End If - End If - - Dim info As TypeInfo - Dim lambda = TryCast(node, LambdaExpressionSyntax) - If lambda IsNot Nothing Then - If SelectionInExpression Then - info = semanticModel.GetTypeInfo(lambda) - Return If(info.Type.IsObjectType(), info.ConvertedType, info.Type) - Else - Return semanticModel.GetLambdaOrAnonymousMethodReturnType(lambda) + End Function + + Public Overrides Function GetReturnType() As (returnType As ITypeSymbol, returnsByRef As Boolean) + ' Todo: consider supporting byref return types in VB + Dim returnType = GetReturnTypeWorker() + Return (returnType, returnsByRef:=False) + End Function + + Private Function GetReturnTypeWorker() As ITypeSymbol + Dim node = Me.GetContainingScope() + Dim semanticModel = Me.SemanticDocument.SemanticModel + + ' special case for collection initializer and explicit cast + If node.IsExpressionInCast() Then + Dim castExpression = TryCast(node.Parent, CastExpressionSyntax) + If castExpression IsNot Nothing Then + Return semanticModel.GetTypeInfo(castExpression.Type).Type + End If End If - End If - expression = DirectCast(node, ExpressionSyntax) - ' regular case. always use ConvertedType to get implicit conversion right. - expression = expression.GetUnparenthesizedExpression() + Dim expression As ExpressionSyntax + If TypeOf node Is CollectionInitializerSyntax Then + expression = node.GetUnparenthesizedExpression() + Return semanticModel.GetTypeInfo(expression).ConvertedType + End If - info = semanticModel.GetTypeInfo(expression) - If info.ConvertedType IsNot Nothing AndAlso - Not info.ConvertedType.IsErrorType() Then - If expression.Kind = SyntaxKind.AddressOfExpression Then - Return info.ConvertedType + Dim methodBlock = TryCast(node, MethodBlockBaseSyntax) + If methodBlock IsNot Nothing Then + Dim symbol = semanticModel.GetDeclaredSymbol(methodBlock.BlockStatement) + Dim propertySymbol = TryCast(symbol, IPropertySymbol) + If propertySymbol IsNot Nothing Then + Return propertySymbol.Type + Else + Return DirectCast(symbol, IMethodSymbol).ReturnType + End If End If - Dim conversion = semanticModel.ClassifyConversion(expression, info.ConvertedType) - If conversion.IsNumeric AndAlso conversion.IsWidening Then - Return info.ConvertedType + Dim info As TypeInfo + Dim lambda = TryCast(node, LambdaExpressionSyntax) + If lambda IsNot Nothing Then + If IsExtractMethodOnExpression Then + info = semanticModel.GetTypeInfo(lambda) + Return If(info.Type.IsObjectType(), info.ConvertedType, info.Type) + Else + Return semanticModel.GetLambdaOrAnonymousMethodReturnType(lambda) + End If End If - Dim conv = semanticModel.GetConversion(expression) - If IsCoClassImplicitConversion(info, conv, semanticModel.Compilation.CoClassType()) Then - Return info.ConvertedType + expression = DirectCast(node, ExpressionSyntax) + ' regular case. always use ConvertedType to get implicit conversion right. + expression = expression.GetUnparenthesizedExpression() + + info = semanticModel.GetTypeInfo(expression) + If info.ConvertedType IsNot Nothing AndAlso + Not info.ConvertedType.IsErrorType() Then + If expression.Kind = SyntaxKind.AddressOfExpression Then + Return info.ConvertedType + End If + + Dim conversion = semanticModel.ClassifyConversion(expression, info.ConvertedType) + If conversion.IsNumeric AndAlso conversion.IsWidening Then + Return info.ConvertedType + End If + + Dim conv = semanticModel.GetConversion(expression) + If IsCoClassImplicitConversion(info, conv, semanticModel.Compilation.CoClassType()) Then + Return info.ConvertedType + End If End If - End If - ' use FormattableString if conversion between String And FormattableString - If If(info.Type?.SpecialType = SpecialType.System_String, False) AndAlso - info.ConvertedType?.IsFormattableStringOrIFormattable() Then + ' use FormattableString if conversion between String And FormattableString + If If(info.Type?.SpecialType = SpecialType.System_String, False) AndAlso + info.ConvertedType?.IsFormattableStringOrIFormattable() Then - Return info.ConvertedType - End If + Return info.ConvertedType + End If - ' get type without considering implicit conversion - Return If(info.Type.IsObjectType(), info.ConvertedType, info.Type) - End Function + ' get type without considering implicit conversion + Return If(info.Type.IsObjectType(), info.ConvertedType, info.Type) + End Function - Private Shared Function IsCoClassImplicitConversion(info As TypeInfo, conversion As Conversion, coclassSymbol As INamedTypeSymbol) As Boolean - If Not conversion.IsWidening OrElse - info.ConvertedType Is Nothing OrElse - info.ConvertedType.TypeKind <> TypeKind.Interface Then - Return False - End If - - ' let's see whether this interface has coclass attribute - Return info.ConvertedType.HasAttribute(coclassSymbol) - End Function - - Public Overrides Function GetFirstStatementUnderContainer() As ExecutableStatementSyntax - Contract.ThrowIfTrue(SelectionInExpression) - - Dim firstToken = GetFirstTokenInSelection() - Dim lastToken = GetLastTokenInSelection() - Dim commonRoot = firstToken.GetCommonRoot(lastToken) - - Dim statement As ExecutableStatementSyntax - If commonRoot.IsStatementContainerNode() Then - Dim firstStatement = GetFirstStatement() - statement = firstStatement.GetAncestorsOrThis(Of ExecutableStatementSyntax) _ - .SkipWhile(Function(s) s.Parent IsNot commonRoot) _ - .First() - If statement.Parent.ContainStatement(statement) Then - Return statement + Private Shared Function IsCoClassImplicitConversion(info As TypeInfo, conversion As Conversion, coclassSymbol As INamedTypeSymbol) As Boolean + If Not conversion.IsWidening OrElse + info.ConvertedType Is Nothing OrElse + info.ConvertedType.TypeKind <> TypeKind.Interface Then + Return False End If - End If - statement = commonRoot.GetStatementUnderContainer() - Contract.ThrowIfNull(statement) + ' let's see whether this interface has coclass attribute + Return info.ConvertedType.HasAttribute(coclassSymbol) + End Function + + Public Overrides Function GetFirstStatementUnderContainer() As ExecutableStatementSyntax + Contract.ThrowIfTrue(IsExtractMethodOnExpression) + + Dim firstToken = GetFirstTokenInSelection() + Dim lastToken = GetLastTokenInSelection() + Dim commonRoot = firstToken.GetCommonRoot(lastToken) + + Dim statement As ExecutableStatementSyntax + If commonRoot.IsStatementContainerNode() Then + Dim firstStatement = GetFirstStatement() + statement = firstStatement.GetAncestorsOrThis(Of ExecutableStatementSyntax) _ + .SkipWhile(Function(s) s.Parent IsNot commonRoot) _ + .First() + If statement.Parent.ContainStatement(statement) Then + Return statement + End If + End If - Return statement - End Function + statement = commonRoot.GetStatementUnderContainer() + Contract.ThrowIfNull(statement) - Public Overrides Function GetLastStatementUnderContainer() As ExecutableStatementSyntax - Contract.ThrowIfTrue(SelectionInExpression) + Return statement + End Function - Dim firstStatement = GetFirstStatementUnderContainer() - Dim container = firstStatement.GetStatementContainer() + Public Overrides Function GetLastStatementUnderContainer() As ExecutableStatementSyntax + Contract.ThrowIfTrue(IsExtractMethodOnExpression) - Dim lastStatement = Me.GetLastStatement().GetAncestorsOrThis(Of ExecutableStatementSyntax) _ - .SkipWhile(Function(s) s.Parent IsNot container) _ - .First() + Dim firstStatement = GetFirstStatementUnderContainer() + Dim container = firstStatement.GetStatementContainer() - Contract.ThrowIfNull(lastStatement) - Contract.ThrowIfFalse(lastStatement.Parent Is (GetFirstStatementUnderContainer()).Parent) + Dim lastStatement = Me.GetLastStatement().GetAncestorsOrThis(Of ExecutableStatementSyntax) _ + .SkipWhile(Function(s) s.Parent IsNot container) _ + .First() - Return lastStatement - End Function + Contract.ThrowIfNull(lastStatement) + Contract.ThrowIfFalse(lastStatement.Parent Is (GetFirstStatementUnderContainer()).Parent) - Public Function InnermostStatementContainer() As SyntaxNode - Contract.ThrowIfFalse(SelectionInExpression) + Return lastStatement + End Function - Dim containingScope = GetContainingScope() - Dim statementContainer = - containingScope.Parent _ - .GetAncestorsOrThis(Of SyntaxNode)() _ - .FirstOrDefault(Function(n) n.IsStatementContainerNode) + Public Function InnermostStatementContainer() As SyntaxNode + Contract.ThrowIfFalse(IsExtractMethodOnExpression) - If statementContainer IsNot Nothing Then - Return statementContainer - End If + Dim containingScope = GetContainingScope() + Dim statementContainer = + containingScope.Parent _ + .GetAncestorsOrThis(Of SyntaxNode)() _ + .FirstOrDefault(Function(n) n.IsStatementContainerNode) - Dim field = containingScope.GetAncestor(Of FieldDeclarationSyntax)() - If field IsNot Nothing Then - Return field.Parent - End If + If statementContainer IsNot Nothing Then + Return statementContainer + End If - Dim [property] = containingScope.GetAncestor(Of PropertyStatementSyntax)() - If [property] IsNot Nothing Then - Return [property].Parent - End If + Dim field = containingScope.GetAncestor(Of FieldDeclarationSyntax)() + If field IsNot Nothing Then + Return field.Parent + End If - ' no repl yet - ' Contract.ThrowIfFalse(last.IsParentKind(SyntaxKind.GlobalStatement)) - ' Contract.ThrowIfFalse(last.Parent.IsParentKind(SyntaxKind.CompilationUnit)) - ' Return last.Parent.Parent - Throw ExceptionUtilities.Unreachable - End Function + Dim [property] = containingScope.GetAncestor(Of PropertyStatementSyntax)() + If [property] IsNot Nothing Then + Return [property].Parent + End If - Public Function IsUnderModuleBlock() As Boolean - Dim currentScope = GetContainingScope() - Dim types = currentScope.GetAncestors(Of TypeBlockSyntax)() + ' no repl yet + ' Contract.ThrowIfFalse(last.IsParentKind(SyntaxKind.GlobalStatement)) + ' Contract.ThrowIfFalse(last.Parent.IsParentKind(SyntaxKind.CompilationUnit)) + ' Return last.Parent.Parent + Throw ExceptionUtilities.Unreachable + End Function - Return types.Any(Function(t) t.BlockStatement.Kind = SyntaxKind.ModuleStatement) - End Function + Public Function IsUnderModuleBlock() As Boolean + Dim currentScope = GetContainingScope() + Dim types = currentScope.GetAncestors(Of TypeBlockSyntax)() - Public Function ContainsInstanceExpression() As Boolean - Dim first = GetFirstTokenInSelection() - Dim last = GetLastTokenInSelection() - Dim node = first.GetCommonRoot(last) + Return types.Any(Function(t) t.BlockStatement.Kind = SyntaxKind.ModuleStatement) + End Function + + Public Function ContainsInstanceExpression() As Boolean + Dim first = GetFirstTokenInSelection() + Dim last = GetLastTokenInSelection() + Dim node = first.GetCommonRoot(last) - Return node.DescendantNodesAndSelf( - TextSpan.FromBounds(first.SpanStart, last.Span.End)) _ - .Any(Function(n) TypeOf n Is InstanceExpressionSyntax) - End Function + Return node.DescendantNodesAndSelf( + TextSpan.FromBounds(first.SpanStart, last.Span.End)) _ + .Any(Function(n) TypeOf n Is InstanceExpressionSyntax) + End Function + End Class End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.Validator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.Validator.vb index 9854cac508bca..a3d339ba1033c 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.Validator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.Validator.vb @@ -7,67 +7,69 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Partial Friend Class VisualBasicSelectionValidator - Public Shared Function Check(semanticModel As SemanticModel, node As SyntaxNode, cancellationToken As CancellationToken) As Boolean - If TypeOf node Is ExpressionSyntax Then - Return CheckExpression(semanticModel, DirectCast(node, ExpressionSyntax), cancellationToken) - ElseIf TypeOf node Is StatementSyntax Then - Return CheckStatement(DirectCast(node, StatementSyntax)) - Else - Return False - End If - End Function + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Partial Friend Class VisualBasicSelectionValidator + Public Shared Function Check(semanticModel As SemanticModel, node As SyntaxNode, cancellationToken As CancellationToken) As Boolean + If TypeOf node Is ExpressionSyntax Then + Return CheckExpression(semanticModel, DirectCast(node, ExpressionSyntax), cancellationToken) + ElseIf TypeOf node Is StatementSyntax Then + Return CheckStatement(DirectCast(node, StatementSyntax)) + Else + Return False + End If + End Function - Private Shared Function CheckExpression(semanticModel As SemanticModel, expression As ExpressionSyntax, cancellationToken As CancellationToken) As Boolean - cancellationToken.ThrowIfCancellationRequested() + Private Shared Function CheckExpression(semanticModel As SemanticModel, expression As ExpressionSyntax, cancellationToken As CancellationToken) As Boolean + cancellationToken.ThrowIfCancellationRequested() - ' TODO(cyrusn): This is probably unnecessary. What we should be doing is binding - ' the type of the expression and seeing if it contains an anonymous type. - If TypeOf expression Is AnonymousObjectCreationExpressionSyntax Then - Return False - End If + ' TODO(cyrusn): This is probably unnecessary. What we should be doing is binding + ' the type of the expression and seeing if it contains an anonymous type. + If TypeOf expression Is AnonymousObjectCreationExpressionSyntax Then + Return False + End If - Return expression.CanReplaceWithRValue(semanticModel, cancellationToken) AndAlso Not expression.ContainsImplicitMemberAccess() - End Function + Return expression.CanReplaceWithRValue(semanticModel, cancellationToken) AndAlso Not expression.ContainsImplicitMemberAccess() + End Function - Private Shared Function CheckStatement(statement As StatementSyntax) As Boolean - If statement.GetAncestor(Of WithBlockSyntax)() IsNot Nothing Then - If statement.ContainsImplicitMemberAccess() Then - Return False + Private Shared Function CheckStatement(statement As StatementSyntax) As Boolean + If statement.GetAncestor(Of WithBlockSyntax)() IsNot Nothing Then + If statement.ContainsImplicitMemberAccess() Then + Return False + End If End If - End If - ' don't support malformed code (bug # 10875) - Dim localDeclaration = TryCast(statement, LocalDeclarationStatementSyntax) - If localDeclaration IsNot Nothing AndAlso localDeclaration.Declarators.Any(Function(d) d.Names.Count > 1 AndAlso d.Initializer IsNot Nothing) Then - Return False - End If + ' don't support malformed code (bug # 10875) + Dim localDeclaration = TryCast(statement, LocalDeclarationStatementSyntax) + If localDeclaration IsNot Nothing AndAlso localDeclaration.Declarators.Any(Function(d) d.Names.Count > 1 AndAlso d.Initializer IsNot Nothing) Then + Return False + End If - If TypeOf statement Is WhileBlockSyntax OrElse - TypeOf statement Is UsingBlockSyntax OrElse - TypeOf statement Is WithBlockSyntax OrElse - TypeOf statement Is ReturnStatementSyntax OrElse - TypeOf statement Is SingleLineIfStatementSyntax OrElse - TypeOf statement Is MultiLineIfBlockSyntax OrElse - TypeOf statement Is TryBlockSyntax OrElse - TypeOf statement Is ErrorStatementSyntax OrElse - TypeOf statement Is SelectBlockSyntax OrElse - TypeOf statement Is DoLoopBlockSyntax OrElse - TypeOf statement Is ForOrForEachBlockSyntax OrElse - TypeOf statement Is ThrowStatementSyntax OrElse - TypeOf statement Is AssignmentStatementSyntax OrElse - TypeOf statement Is CallStatementSyntax OrElse - TypeOf statement Is ExpressionStatementSyntax OrElse - TypeOf statement Is AddRemoveHandlerStatementSyntax OrElse - TypeOf statement Is RaiseEventStatementSyntax OrElse - TypeOf statement Is ReDimStatementSyntax OrElse - TypeOf statement Is EraseStatementSyntax OrElse - TypeOf statement Is LocalDeclarationStatementSyntax OrElse - TypeOf statement Is SyncLockBlockSyntax Then - Return True - End If + If TypeOf statement Is WhileBlockSyntax OrElse + TypeOf statement Is UsingBlockSyntax OrElse + TypeOf statement Is WithBlockSyntax OrElse + TypeOf statement Is ReturnStatementSyntax OrElse + TypeOf statement Is SingleLineIfStatementSyntax OrElse + TypeOf statement Is MultiLineIfBlockSyntax OrElse + TypeOf statement Is TryBlockSyntax OrElse + TypeOf statement Is ErrorStatementSyntax OrElse + TypeOf statement Is SelectBlockSyntax OrElse + TypeOf statement Is DoLoopBlockSyntax OrElse + TypeOf statement Is ForOrForEachBlockSyntax OrElse + TypeOf statement Is ThrowStatementSyntax OrElse + TypeOf statement Is AssignmentStatementSyntax OrElse + TypeOf statement Is CallStatementSyntax OrElse + TypeOf statement Is ExpressionStatementSyntax OrElse + TypeOf statement Is AddRemoveHandlerStatementSyntax OrElse + TypeOf statement Is RaiseEventStatementSyntax OrElse + TypeOf statement Is ReDimStatementSyntax OrElse + TypeOf statement Is EraseStatementSyntax OrElse + TypeOf statement Is LocalDeclarationStatementSyntax OrElse + TypeOf statement Is SyncLockBlockSyntax Then + Return True + End If - Return False - End Function + Return False + End Function + End Class End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.vb index 1487863ab2ab3..aff748a1ec831 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.vb @@ -2,658 +2,588 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.ExtractMethod -Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod - Friend Class VisualBasicSelectionValidator - Inherits SelectionValidator(Of VisualBasicSelectionResult, ExecutableStatementSyntax) - - Public Sub New(document As SemanticDocument, - textSpan As TextSpan) - MyBase.New(document, textSpan) - End Sub - - Public Overrides Async Function GetValidSelectionAsync(cancellationToken As CancellationToken) As Task(Of (VisualBasicSelectionResult, OperationStatus)) - If Not ContainsValidSelection Then - Return (Nothing, OperationStatus.FailedWithUnknownReason) - End If - - Dim text = Me.SemanticDocument.Text - Dim root = SemanticDocument.Root - Dim model = Me.SemanticDocument.SemanticModel - - Dim selectionInfo = GetInitialSelectionInfo(root) - selectionInfo = AssignInitialFinalTokens(selectionInfo, root, cancellationToken) - selectionInfo = AdjustFinalTokensBasedOnContext(selectionInfo, model, cancellationToken) - selectionInfo = AdjustFinalTokensIfNextStatement(selectionInfo, model, cancellationToken) - selectionInfo = FixUpFinalTokensAndAssignFinalSpan(selectionInfo, root, cancellationToken) - selectionInfo = CheckErrorCasesAndAppendDescriptions(selectionInfo, model, cancellationToken) - - If selectionInfo.Status.Failed() Then - Return (Nothing, selectionInfo.Status) - End If - - Dim controlFlowSpan = GetControlFlowSpan(selectionInfo) - If Not selectionInfo.SelectionInExpression Then - Dim statementRange = GetStatementRangeContainedInSpan(Of StatementSyntax)(root, controlFlowSpan, cancellationToken) - If statementRange Is Nothing Then - With selectionInfo - .Status = .Status.With(succeeded:=False, VBFeaturesResources.can_t_determine_valid_range_of_statements_to_extract_out) - End With - - Return (Nothing, selectionInfo.Status) - End If - - Dim isFinalSpanSemanticallyValid = IsFinalSpanSemanticallyValidSpan(model, controlFlowSpan, statementRange.Value, cancellationToken) - If Not isFinalSpanSemanticallyValid Then - ' check control flow only if we are extracting statement level, not expression level. - ' you can't have goto that moves control out of scope in expression level (even in lambda) - With selectionInfo - .Status = .Status.With(succeeded:=True, FeaturesResources.Not_all_code_paths_return) - End With - End If - End If - - Dim result = Await VisualBasicSelectionResult.CreateResultAsync( - selectionInfo.OriginalSpan, - selectionInfo.FinalSpan, - selectionInfo.SelectionInExpression, - Me.SemanticDocument, - selectionInfo.FirstTokenInFinalSpan, - selectionInfo.LastTokenInFinalSpan, - SelectionChanged(selectionInfo), - cancellationToken).ConfigureAwait(False) - Return (result, selectionInfo.Status) - End Function - - Private Shared Function GetControlFlowSpan(selectionInfo As SelectionInfo) As TextSpan - Return TextSpan.FromBounds(selectionInfo.FirstTokenInFinalSpan.SpanStart, selectionInfo.LastTokenInFinalSpan.Span.End) - End Function - - Private Shared Function CheckErrorCasesAndAppendDescriptions(selectionInfo As SelectionInfo, semanticModel As SemanticModel, cancellationToken As CancellationToken) As SelectionInfo - If selectionInfo.Status.Failed() Then - Return selectionInfo - End If - - Dim clone = selectionInfo.Clone() - - If selectionInfo.FirstTokenInFinalSpan.IsMissing OrElse selectionInfo.LastTokenInFinalSpan.IsMissing Then - With clone - .Status = .Status.With(succeeded:=False, VBFeaturesResources.contains_invalid_selection) - End With - End If - - ' get the node that covers the selection - Dim commonNode = GetFinalTokenCommonRoot(selectionInfo) - - If (selectionInfo.SelectionInExpression OrElse selectionInfo.SelectionInSingleStatement) AndAlso commonNode.HasDiagnostics() Then - With clone - .Status = .Status.With(succeeded:=False, VBFeaturesResources.the_selection_contains_syntactic_errors) - End With - End If - - Dim root = semanticModel.SyntaxTree.GetRoot(cancellationToken) - Dim tokens = root.DescendantTokens(selectionInfo.FinalSpan) - If tokens.ContainPreprocessorCrossOver(selectionInfo.FinalSpan) Then - With clone - .Status = .Status.With(succeeded:=True, VBFeaturesResources.Selection_can_t_be_crossed_over_preprocessors) - End With - End If - - ' TODO : check behavior of control flow analysis engine around exception and exception handling. - If tokens.ContainArgumentlessThrowWithoutEnclosingCatch(selectionInfo.FinalSpan) Then - With clone - .Status = .Status.With(succeeded:=True, VBFeaturesResources.Selection_can_t_contain_throw_without_enclosing_catch_block) - End With - End If - - If selectionInfo.SelectionInExpression AndAlso commonNode.PartOfConstantInitializerExpression() Then - With clone - .Status = .Status.With(succeeded:=False, VBFeaturesResources.Selection_can_t_be_parts_of_constant_initializer_expression) - End With - End If - - If selectionInfo.SelectionInExpression AndAlso commonNode.IsArgumentForByRefParameter(semanticModel, cancellationToken) Then - With clone - .Status = .Status.With(succeeded:=True, VBFeaturesResources.Argument_used_for_ByRef_parameter_can_t_be_extracted_out) - End With - End If - - Dim containsAllStaticLocals = ContainsAllStaticLocalUsagesDefinedInSelectionIfExist(selectionInfo, semanticModel, cancellationToken) - If Not containsAllStaticLocals Then - With clone - .Status = .Status.With(succeeded:=True, VBFeaturesResources.all_static_local_usages_defined_in_the_selection_must_be_included_in_the_selection) - End With - End If - - ' if it is multiple statement case. - If Not selectionInfo.SelectionInExpression AndAlso Not selectionInfo.SelectionInSingleStatement Then - If commonNode.GetAncestorOrThis(Of WithBlockSyntax)() IsNot Nothing Then - If commonNode.GetImplicitMemberAccessExpressions(selectionInfo.FinalSpan).Any() Then - With clone - .Status = .Status.With(succeeded:=True, VBFeaturesResources.Implicit_member_access_can_t_be_included_in_the_selection_without_containing_statement) - End With - End If - End If - End If + Partial Friend NotInheritable Class VisualBasicExtractMethodService + Friend NotInheritable Class VisualBasicSelectionValidator + Inherits SelectionValidator - If Not selectionInfo.SelectionInExpression AndAlso Not selectionInfo.SelectionInSingleStatement Then - If selectionInfo.FirstTokenInFinalSpan.GetAncestor(Of ExecutableStatementSyntax)() Is Nothing OrElse - selectionInfo.LastTokenInFinalSpan.GetAncestor(Of ExecutableStatementSyntax)() Is Nothing Then - With clone - .Status = .Status.With(succeeded:=False, VBFeaturesResources.Selection_must_be_part_of_executable_statements) - End With - End If - End If + Public Sub New(document As SemanticDocument, textSpan As TextSpan) + MyBase.New(document, textSpan) + End Sub - Return clone - End Function + Protected Overrides Function AreStatementsInSameContainer(statement1 As StatementSyntax, statement2 As StatementSyntax) As Boolean + Return statement1.Parent Is statement2.Parent + End Function - Private Shared Function SelectionChanged(selectionInfo As SelectionInfo) As Boolean - ' get final token that doesn't pointing to empty token - Dim finalFirstToken = If(selectionInfo.FirstTokenInFinalSpan.Width = 0, - selectionInfo.FirstTokenInFinalSpan.GetNextToken(), - selectionInfo.FirstTokenInFinalSpan) + Protected Overrides Function GetInitialSelectionInfo(cancellationToken As CancellationToken) As SelectionInfo + Dim root = Me.SemanticDocument.Root + Dim model = Me.SemanticDocument.SemanticModel - Dim finalLastToken = If(selectionInfo.LastTokenInFinalSpan.Width = 0, - selectionInfo.LastTokenInFinalSpan.GetPreviousToken(), - selectionInfo.LastTokenInFinalSpan) + Dim selectionInfo = GetInitialSelectionInfo(root) + selectionInfo = AssignInitialFinalTokens(selectionInfo, root, cancellationToken) + selectionInfo = AdjustFinalTokensBasedOnContext(selectionInfo, model, cancellationToken) + selectionInfo = AdjustFinalTokensIfNextStatement(selectionInfo, model, cancellationToken) + selectionInfo = FixUpFinalTokensAndAssignFinalSpan(selectionInfo, root, cancellationToken) + selectionInfo = CheckErrorCasesAndAppendDescriptions(selectionInfo, model, cancellationToken) - ' adjust original tokens to point to statement terminator token if needed - Dim originalFirstToken = selectionInfo.FirstTokenInOriginalSpan + Return selectionInfo + End Function + + Protected Overrides Function CreateSelectionResultAsync( + selectionInfo As SelectionInfo, + cancellationToken As CancellationToken) As Task(Of VisualBasicSelectionResult) + + Contract.ThrowIfFalse(ContainsValidSelection) + Contract.ThrowIfFalse(selectionInfo.Status.Succeeded) + + Return VisualBasicSelectionResult.CreateResultAsync( + Me.SemanticDocument, + selectionInfo, + SelectionChanged(selectionInfo), + cancellationToken) + End Function + + Private Shared Function CheckErrorCasesAndAppendDescriptions( + selectionInfo As SelectionInfo, + semanticModel As SemanticModel, + cancellationToken As CancellationToken) As SelectionInfo + If selectionInfo.Status.Failed() Then + Return selectionInfo + End If - Dim originalLastToken = selectionInfo.LastTokenInOriginalSpan + Dim clone = selectionInfo - Return originalFirstToken <> finalFirstToken OrElse originalLastToken <> finalLastToken - End Function + If selectionInfo.FirstTokenInFinalSpan.IsMissing OrElse selectionInfo.LastTokenInFinalSpan.IsMissing Then + clone = clone.With( + status:=clone.Status.With(succeeded:=False, VBFeaturesResources.contains_invalid_selection)) + End If - Private Shared Function ContainsAllStaticLocalUsagesDefinedInSelectionIfExist(selectionInfo As SelectionInfo, - semanticModel As SemanticModel, - cancellationToken As CancellationToken) As Boolean - If selectionInfo.FirstTokenInFinalSpan.GetAncestor(Of FieldDeclarationSyntax)() IsNot Nothing OrElse - selectionInfo.FirstTokenInFinalSpan.GetAncestor(Of PropertyStatementSyntax)() IsNot Nothing Then - ' static local can't exist in field initializer - Return True - End If + ' get the node that covers the selection + Dim commonNode = GetFinalTokenCommonRoot(selectionInfo) - Dim result As DataFlowAnalysis + If (selectionInfo.SelectionInExpression OrElse selectionInfo.SelectionInSingleStatement) AndAlso commonNode.HasDiagnostics() Then + clone = clone.With( + status:=clone.Status.With(succeeded:=False, VBFeaturesResources.the_selection_contains_syntactic_errors)) + End If - If selectionInfo.SelectionInExpression Then - Dim expression = GetFinalTokenCommonRoot(selectionInfo).GetAncestorOrThis(Of ExpressionSyntax)() - result = semanticModel.AnalyzeDataFlow(expression) - Else - Dim range = GetStatementRangeContainedInSpan(Of StatementSyntax)( - semanticModel.SyntaxTree.GetRoot(cancellationToken), GetControlFlowSpan(selectionInfo), cancellationToken) + Dim root = semanticModel.SyntaxTree.GetRoot(cancellationToken) + Dim tokens = root.DescendantTokens(selectionInfo.FinalSpan) + If tokens.ContainPreprocessorCrossOver(selectionInfo.FinalSpan) Then + clone = clone.With( + status:=clone.Status.With(succeeded:=True, VBFeaturesResources.Selection_can_t_be_crossed_over_preprocessors)) + End If - ' we can't determine valid range of statements, don't bother to do the analysis - If range Is Nothing Then - Return True + ' TODO : check behavior of control flow analysis engine around exception and exception handling. + If tokens.ContainArgumentlessThrowWithoutEnclosingCatch(selectionInfo.FinalSpan) Then + clone = clone.With( + status:=clone.Status.With(succeeded:=True, VBFeaturesResources.Selection_can_t_contain_throw_without_enclosing_catch_block)) End If - result = semanticModel.AnalyzeDataFlow(range.Value.Item1, range.Value.Item2) - End If + If selectionInfo.SelectionInExpression AndAlso commonNode.PartOfConstantInitializerExpression() Then + clone = clone.With( + status:=clone.Status.With(succeeded:=False, VBFeaturesResources.Selection_can_t_be_parts_of_constant_initializer_expression)) + End If - For Each symbol In result.VariablesDeclared - Dim local = TryCast(symbol, ILocalSymbol) - If local Is Nothing Then - Continue For + If selectionInfo.SelectionInExpression AndAlso commonNode.IsArgumentForByRefParameter(semanticModel, cancellationToken) Then + clone = clone.With( + status:=clone.Status.With(succeeded:=True, VBFeaturesResources.Argument_used_for_ByRef_parameter_can_t_be_extracted_out)) End If - If Not local.IsStatic Then - Continue For + Dim containsAllStaticLocals = ContainsAllStaticLocalUsagesDefinedInSelectionIfExist(selectionInfo, semanticModel, cancellationToken) + If Not containsAllStaticLocals Then + clone = clone.With( + status:=clone.Status.With(succeeded:=True, VBFeaturesResources.all_static_local_usages_defined_in_the_selection_must_be_included_in_the_selection)) End If - If result.WrittenOutside().Any(Function(s) Equals(s, local)) OrElse -result.ReadOutside().Any(Function(s) Equals(s, local)) Then - Return False + ' if it is multiple statement case. + If Not selectionInfo.SelectionInExpression AndAlso Not selectionInfo.SelectionInSingleStatement Then + If commonNode.GetAncestorOrThis(Of WithBlockSyntax)() IsNot Nothing Then + If commonNode.GetImplicitMemberAccessExpressions(selectionInfo.FinalSpan).Any() Then + clone = clone.With( + status:=clone.Status.With(succeeded:=True, VBFeaturesResources.Implicit_member_access_can_t_be_included_in_the_selection_without_containing_statement)) + End If + End If End If - Next - Return True - End Function + If Not selectionInfo.SelectionInExpression AndAlso Not selectionInfo.SelectionInSingleStatement Then + If selectionInfo.FirstTokenInFinalSpan.GetAncestor(Of ExecutableStatementSyntax)() Is Nothing OrElse + selectionInfo.LastTokenInFinalSpan.GetAncestor(Of ExecutableStatementSyntax)() Is Nothing Then + clone = clone.With( + status:=clone.Status.With(succeeded:=False, VBFeaturesResources.Selection_must_be_part_of_executable_statements)) + End If + End If - Private Shared Function GetFinalTokenCommonRoot(selection As SelectionInfo) As SyntaxNode - Return GetCommonRoot(selection.FirstTokenInFinalSpan, selection.LastTokenInFinalSpan) - End Function + Return clone + End Function - Private Shared Function GetCommonRoot(token1 As SyntaxToken, token2 As SyntaxToken) As SyntaxNode - Return token1.GetCommonRoot(token2) - End Function + Private Shared Function SelectionChanged(selectionInfo As SelectionInfo) As Boolean + ' get final token that doesn't pointing to empty token + Dim finalFirstToken = If(selectionInfo.FirstTokenInFinalSpan.Width = 0, + selectionInfo.FirstTokenInFinalSpan.GetNextToken(), + selectionInfo.FirstTokenInFinalSpan) - Private Shared Function FixUpFinalTokensAndAssignFinalSpan(selectionInfo As SelectionInfo, - root As SyntaxNode, - cancellationToken As CancellationToken) As SelectionInfo - If selectionInfo.Status.Failed() Then - Return selectionInfo - End If + Dim finalLastToken = If(selectionInfo.LastTokenInFinalSpan.Width = 0, + selectionInfo.LastTokenInFinalSpan.GetPreviousToken(), + selectionInfo.LastTokenInFinalSpan) - Dim clone = selectionInfo.Clone() + ' adjust original tokens to point to statement terminator token if needed + Dim originalFirstToken = selectionInfo.FirstTokenInOriginalSpan - ' make sure we include statement terminator token if selection contains them - Dim firstToken = selectionInfo.FirstTokenInFinalSpan - Dim lastToken = selectionInfo.LastTokenInFinalSpan + Dim originalLastToken = selectionInfo.LastTokenInOriginalSpan - ' set final span - Dim start = If(selectionInfo.OriginalSpan.Start <= firstToken.SpanStart, selectionInfo.OriginalSpan.Start, firstToken.FullSpan.Start) - Dim [end] = If(lastToken.Span.End <= selectionInfo.OriginalSpan.End, selectionInfo.OriginalSpan.End, lastToken.Span.End) + Return originalFirstToken <> finalFirstToken OrElse originalLastToken <> finalLastToken + End Function - With clone - .FinalSpan = GetAdjustedSpan(root, TextSpan.FromBounds(start, [end])) - .FirstTokenInFinalSpan = firstToken - .LastTokenInFinalSpan = lastToken - End With + Private Shared Function ContainsAllStaticLocalUsagesDefinedInSelectionIfExist( + selectionInfo As SelectionInfo, + semanticModel As SemanticModel, + cancellationToken As CancellationToken) As Boolean + If selectionInfo.FirstTokenInFinalSpan.GetAncestor(Of FieldDeclarationSyntax)() IsNot Nothing OrElse + selectionInfo.FirstTokenInFinalSpan.GetAncestor(Of PropertyStatementSyntax)() IsNot Nothing Then + ' static local can't exist in field initializer + Return True + End If - Return clone - End Function + Dim result As DataFlowAnalysis - Private Shared Function AdjustFinalTokensIfNextStatement(selectionInfo As SelectionInfo, - semanticModel As SemanticModel, - cancellationToken As CancellationToken) As SelectionInfo - If selectionInfo.Status.Failed() Then - Return selectionInfo - End If + If selectionInfo.SelectionInExpression Then + Dim expression = GetFinalTokenCommonRoot(selectionInfo).GetAncestorOrThis(Of ExpressionSyntax)() + result = semanticModel.AnalyzeDataFlow(expression) + Else + Dim range = GetStatementRangeContainedInSpan( + semanticModel.SyntaxTree.GetRoot(cancellationToken), selectionInfo.GetControlFlowSpan(), cancellationToken) - ' if last statement is next statement, make sure its corresponding loop statement is - ' included - Dim nextStatement = selectionInfo.LastTokenInFinalSpan.GetAncestor(Of NextStatementSyntax)() - If nextStatement Is Nothing OrElse nextStatement.ControlVariables.Count < 2 Then - Return selectionInfo - End If + ' we can't determine valid range of statements, don't bother to do the analysis + If range Is Nothing Then + Return True + End If - Dim clone = selectionInfo.Clone() - Dim outmostControlVariable = nextStatement.ControlVariables.Last + result = semanticModel.AnalyzeDataFlow(range.Value.Item1, range.Value.Item2) + End If - Dim symbolInfo = semanticModel.GetSymbolInfo(outmostControlVariable, cancellationToken) - Dim symbol = symbolInfo.GetBestOrAllSymbols().FirstOrDefault() + For Each symbol In result.VariablesDeclared + Dim local = TryCast(symbol, ILocalSymbol) + If local Is Nothing Then + Continue For + End If - ' can't find symbol for the control variable. don't provide extract method - If symbol Is Nothing OrElse - symbol.Locations.Length <> 1 OrElse - Not symbol.Locations.First().IsInSource OrElse - symbol.Locations.First().SourceTree IsNot semanticModel.SyntaxTree Then - With clone - .Status = .Status.With(succeeded:=False, VBFeaturesResources.next_statement_control_variable_doesn_t_have_matching_declaration_statement) - End With + If Not local.IsStatic Then + Continue For + End If - Return clone - End If + If result.WrittenOutside().Any(Function(s) Equals(s, local)) OrElse + result.ReadOutside().Any(Function(s) Equals(s, local)) Then + Return False + End If + Next - Dim startPosition = symbol.Locations.First().SourceSpan.Start - Dim root = semanticModel.SyntaxTree.GetRoot(cancellationToken) - Dim forBlock = root.FindToken(startPosition).GetAncestor(Of ForOrForEachBlockSyntax)() - If forBlock Is Nothing Then - With clone - .Status = .Status.With(succeeded:=False, VBFeaturesResources.next_statement_control_variable_doesn_t_have_matching_declaration_statement) - End With + Return True + End Function + + Private Shared Function GetFinalTokenCommonRoot(selection As SelectionInfo) As SyntaxNode + Return GetCommonRoot(selection.FirstTokenInFinalSpan, selection.LastTokenInFinalSpan) + End Function + + Private Shared Function GetCommonRoot(token1 As SyntaxToken, token2 As SyntaxToken) As SyntaxNode + Return token1.GetCommonRoot(token2) + End Function + + Private Shared Function FixUpFinalTokensAndAssignFinalSpan( + selectionInfo As SelectionInfo, + root As SyntaxNode, + cancellationToken As CancellationToken) As SelectionInfo + If selectionInfo.Status.Failed() Then + Return selectionInfo + End If - Return clone - End If - - Dim firstStatement = forBlock.ForOrForEachStatement - With clone - .SelectionInExpression = False - .SelectionInSingleStatement = forBlock.Span.Contains(nextStatement.Span) - .FirstTokenInFinalSpan = firstStatement.GetFirstToken(includeZeroWidth:=True) - .LastTokenInFinalSpan = nextStatement.GetLastToken(includeZeroWidth:=True) - End With - - Return clone - End Function - - Private Shared Function AdjustFinalTokensBasedOnContext(selectionInfo As SelectionInfo, - semanticModel As SemanticModel, - cancellationToken As CancellationToken) As SelectionInfo - If selectionInfo.Status.Failed() Then - Return selectionInfo - End If + Dim clone = selectionInfo - ' don't need to adjust anything if it is multi-statements case - If (Not selectionInfo.SelectionInExpression) AndAlso (Not selectionInfo.SelectionInSingleStatement) Then - Return selectionInfo - End If + ' make sure we include statement terminator token if selection contains them + Dim firstToken = selectionInfo.FirstTokenInFinalSpan + Dim lastToken = selectionInfo.LastTokenInFinalSpan - Dim clone = selectionInfo.Clone() + ' set final span + Dim start = If(selectionInfo.OriginalSpan.Start <= firstToken.SpanStart, selectionInfo.OriginalSpan.Start, firstToken.FullSpan.Start) + Dim [end] = If(lastToken.Span.End <= selectionInfo.OriginalSpan.End, selectionInfo.OriginalSpan.End, lastToken.Span.End) - ' get the node that covers the selection - Dim node = GetFinalTokenCommonRoot(selectionInfo) + Return clone.With( + finalSpan:=GetAdjustedSpan(root, TextSpan.FromBounds(start, [end])), + firstTokenInFinalSpan:=firstToken, + lastTokenInFinalSpan:=lastToken) + End Function - Dim validNode = Check(semanticModel, node, cancellationToken) - If validNode Then - Return selectionInfo - End If + Private Shared Function AdjustFinalTokensIfNextStatement( + selectionInfo As SelectionInfo, + semanticModel As SemanticModel, + cancellationToken As CancellationToken) As SelectionInfo + If selectionInfo.Status.Failed() Then + Return selectionInfo + End If - Dim firstValidNode = node.GetAncestors(Of SyntaxNode)().FirstOrDefault( - Function(n) Check(semanticModel, n, cancellationToken)) + ' if last statement is next statement, make sure its corresponding loop statement is + ' included + Dim nextStatement = selectionInfo.LastTokenInFinalSpan.GetAncestor(Of NextStatementSyntax)() + If nextStatement Is Nothing OrElse nextStatement.ControlVariables.Count < 2 Then + Return selectionInfo + End If - If firstValidNode Is Nothing Then - ' couldn't find any valid node - With clone - .Status = New OperationStatus(succeeded:=False, VBFeaturesResources.Selection_doesn_t_contain_any_valid_node) - .FirstTokenInFinalSpan = Nothing - .LastTokenInFinalSpan = Nothing - End With + Dim outmostControlVariable = nextStatement.ControlVariables.Last - Return clone - End If + Dim symbolInfo = semanticModel.GetSymbolInfo(outmostControlVariable, cancellationToken) + Dim symbol = symbolInfo.GetBestOrAllSymbols().FirstOrDefault() - With clone - .SelectionInExpression = TypeOf firstValidNode Is ExpressionSyntax - .SelectionInSingleStatement = TypeOf firstValidNode Is StatementSyntax - .FirstTokenInFinalSpan = firstValidNode.GetFirstToken(includeZeroWidth:=True) - .LastTokenInFinalSpan = firstValidNode.GetLastToken(includeZeroWidth:=True) - End With + ' can't find symbol for the control variable. don't provide extract method + If symbol Is Nothing OrElse + symbol.Locations.Length <> 1 OrElse + Not symbol.Locations.First().IsInSource OrElse + symbol.Locations.First().SourceTree IsNot semanticModel.SyntaxTree Then + Return selectionInfo.With( + status:=selectionInfo.Status.With(succeeded:=False, VBFeaturesResources.next_statement_control_variable_doesn_t_have_matching_declaration_statement)) + End If - Return clone - End Function + Dim startPosition = symbol.Locations.First().SourceSpan.Start + Dim root = semanticModel.SyntaxTree.GetRoot(cancellationToken) + Dim forBlock = root.FindToken(startPosition).GetAncestor(Of ForOrForEachBlockSyntax)() + If forBlock Is Nothing Then + Return selectionInfo.With( + status:=selectionInfo.Status.With(succeeded:=False, VBFeaturesResources.next_statement_control_variable_doesn_t_have_matching_declaration_statement)) + End If - Private Shared Function AssignInitialFinalTokens(selectionInfo As SelectionInfo, root As SyntaxNode, cancellationToken As CancellationToken) As SelectionInfo - If selectionInfo.Status.Failed() Then - Return selectionInfo - End If + Dim firstStatement = forBlock.ForOrForEachStatement + Return selectionInfo.With( + selectionInExpression:=False, + selectionInSingleStatement:=forBlock.Span.Contains(nextStatement.Span), + firstTokenInFinalSpan:=firstStatement.GetFirstToken(includeZeroWidth:=True), + lastTokenInFinalSpan:=nextStatement.GetLastToken(includeZeroWidth:=True)) + End Function + + Private Shared Function AdjustFinalTokensBasedOnContext( + selectionInfo As SelectionInfo, + semanticModel As SemanticModel, + cancellationToken As CancellationToken) As SelectionInfo + If selectionInfo.Status.Failed() Then + Return selectionInfo + End If - Dim clone = selectionInfo.Clone() + ' don't need to adjust anything if it is multi-statements case + If (Not selectionInfo.SelectionInExpression) AndAlso (Not selectionInfo.SelectionInSingleStatement) Then + Return selectionInfo + End If - If selectionInfo.SelectionInExpression Then - ' prefer outer statement or expression if two has same span - Dim outerNode = selectionInfo.CommonRootFromOriginalSpan.GetOutermostNodeWithSameSpan(Function(n) TypeOf n Is StatementSyntax OrElse TypeOf n Is ExpressionSyntax) + ' get the node that covers the selection + Dim node = GetFinalTokenCommonRoot(selectionInfo) - ' simple expression case - With clone - .SelectionInExpression = TypeOf outerNode Is ExpressionSyntax - .SelectionInSingleStatement = TypeOf outerNode Is StatementSyntax - .FirstTokenInFinalSpan = outerNode.GetFirstToken(includeZeroWidth:=True) - .LastTokenInFinalSpan = outerNode.GetLastToken(includeZeroWidth:=True) - End With + Dim validNode = Check(semanticModel, node, cancellationToken) + If validNode Then + Return selectionInfo + End If - Return clone - End If + Dim firstValidNode = node.GetAncestors(Of SyntaxNode)().FirstOrDefault( + Function(n) Check(semanticModel, n, cancellationToken)) - Dim range = GetStatementRangeContainingSpan(Of StatementSyntax)( - VisualBasicSyntaxFacts.Instance, - root, TextSpan.FromBounds(selectionInfo.FirstTokenInOriginalSpan.SpanStart, selectionInfo.LastTokenInOriginalSpan.Span.End), - cancellationToken) + If firstValidNode Is Nothing Then + ' couldn't find any valid node + Return selectionInfo.With( + status:=New OperationStatus(succeeded:=False, VBFeaturesResources.Selection_doesn_t_contain_any_valid_node), + firstTokenInFinalSpan:=Nothing, + lastTokenInFinalSpan:=Nothing) + End If - If range Is Nothing Then - With clone - .Status = clone.Status.With(succeeded:=False, VBFeaturesResources.no_valid_statement_range_to_extract_out) - End With + Return selectionInfo.With( + selectionInExpression:=TypeOf firstValidNode Is ExpressionSyntax, + selectionInSingleStatement:=TypeOf firstValidNode Is StatementSyntax, + firstTokenInFinalSpan:=firstValidNode.GetFirstToken(includeZeroWidth:=True), + lastTokenInFinalSpan:=firstValidNode.GetLastToken(includeZeroWidth:=True)) + End Function + + Private Function AssignInitialFinalTokens( + selectionInfo As SelectionInfo, + root As SyntaxNode, + cancellationToken As CancellationToken) As SelectionInfo + If selectionInfo.Status.Failed() Then + Return selectionInfo + End If - Return clone - End If + If selectionInfo.SelectionInExpression Then + ' prefer outer statement or expression if two has same span + Dim outerNode = selectionInfo.CommonRootFromOriginalSpan.GetOutermostNodeWithSameSpan(Function(n) TypeOf n Is StatementSyntax OrElse TypeOf n Is ExpressionSyntax) - Dim statement1 = DirectCast(range.Value.Item1, StatementSyntax) - Dim statement2 = DirectCast(range.Value.Item2, StatementSyntax) + ' simple expression case + Return selectionInfo.With( + selectionInExpression:=TypeOf outerNode Is ExpressionSyntax, + selectionInSingleStatement:=TypeOf outerNode Is StatementSyntax, + firstTokenInFinalSpan:=outerNode.GetFirstToken(includeZeroWidth:=True), + lastTokenInFinalSpan:=outerNode.GetLastToken(includeZeroWidth:=True)) + End If - If statement1 Is statement2 Then - ' check one more time to see whether it is an expression case - Dim expression = selectionInfo.CommonRootFromOriginalSpan.GetAncestor(Of ExpressionSyntax)() - If expression IsNot Nothing AndAlso statement1.Span.Contains(expression.Span) Then - With clone - .SelectionInExpression = True - .FirstTokenInFinalSpan = expression.GetFirstToken(includeZeroWidth:=True) - .LastTokenInFinalSpan = expression.GetLastToken(includeZeroWidth:=True) - End With + Dim range = GetStatementRangeContainingSpan( + root, TextSpan.FromBounds(selectionInfo.FirstTokenInOriginalSpan.SpanStart, selectionInfo.LastTokenInOriginalSpan.Span.End), + cancellationToken) - Return clone + If range Is Nothing Then + Return selectionInfo.With( + status:=selectionInfo.Status.With(succeeded:=False, FeaturesResources.No_valid_statement_range_to_extract)) End If - ' single statement case - ' current way to find out a statement that can be extracted out - Dim singleStatement = statement1.GetAncestorsOrThis(Of StatementSyntax)().FirstOrDefault( - Function(s) s.Parent IsNot Nothing AndAlso s.Parent.IsStatementContainerNode() AndAlso s.Parent.ContainStatement(s)) - - If singleStatement Is Nothing Then - With clone - .Status = clone.Status.With(succeeded:=False, VBFeaturesResources.no_valid_statement_range_to_extract_out) - End With + Dim statement1 = range.Value.firstStatement + Dim statement2 = range.Value.lastStatement + + If statement1 Is statement2 Then + ' check one more time to see whether it is an expression case + Dim expression = selectionInfo.CommonRootFromOriginalSpan.GetAncestor(Of ExpressionSyntax)() + If expression IsNot Nothing AndAlso statement1.Span.Contains(expression.Span) Then + Return selectionInfo.With( + selectionInExpression:=True, + firstTokenInFinalSpan:=expression.GetFirstToken(includeZeroWidth:=True), + lastTokenInFinalSpan:=expression.GetLastToken(includeZeroWidth:=True)) + End If - Return clone - End If + ' single statement case + ' current way to find out a statement that can be extracted out + Dim singleStatement = statement1.GetAncestorsOrThis(Of StatementSyntax)().FirstOrDefault( + Function(s) s.Parent IsNot Nothing AndAlso s.Parent.IsStatementContainerNode() AndAlso s.Parent.ContainStatement(s)) - With clone - .SelectionInSingleStatement = True - .FirstTokenInFinalSpan = singleStatement.GetFirstToken(includeZeroWidth:=True) - .LastTokenInFinalSpan = singleStatement.GetLastToken(includeZeroWidth:=True) - End With + If singleStatement Is Nothing Then + Return selectionInfo.With( + status:=selectionInfo.Status.With(succeeded:=False, FeaturesResources.No_valid_statement_range_to_extract)) + End If - Return clone - End If + Return selectionInfo.With( + selectionInSingleStatement:=True, + firstTokenInFinalSpan:=singleStatement.GetFirstToken(includeZeroWidth:=True), + lastTokenInFinalSpan:=singleStatement.GetLastToken(includeZeroWidth:=True)) + End If - ' Special check for vb - ' either statement1 or statement2 is pointing to header and end of a block node - ' return the block instead of each node - If statement1.Parent.IsStatementContainerNode() Then - Dim contain1 = statement1.Parent.ContainStatement(statement1) - Dim contain2 = statement2.Parent.ContainStatement(statement2) + ' Special check for vb + ' either statement1 or statement2 is pointing to header and end of a block node + ' return the block instead of each node + If statement1.Parent.IsStatementContainerNode() Then + Dim contain1 = statement1.Parent.ContainStatement(statement1) + Dim contain2 = statement2.Parent.ContainStatement(statement2) + + If Not contain1 OrElse Not contain2 Then + Dim parent = statement1.Parent _ + .GetAncestorsOrThis(Of SyntaxNode)() _ + .Where(Function(n) TypeOf n Is ExpressionSyntax OrElse TypeOf n Is StatementSyntax) _ + .First() + + ' single statement case + Return selectionInfo.With( + selectionInExpression:=TypeOf parent Is ExpressionSyntax, + selectionInSingleStatement:=TypeOf parent Is StatementSyntax, + firstTokenInFinalSpan:=parent.GetFirstToken(), + lastTokenInFinalSpan:=parent.GetLastToken()) + End If + End If - If Not contain1 OrElse Not contain2 Then - Dim parent = statement1.Parent _ - .GetAncestorsOrThis(Of SyntaxNode)() _ - .Where(Function(n) TypeOf n Is ExpressionSyntax OrElse TypeOf n Is StatementSyntax) _ - .First() + Return selectionInfo.With( + firstTokenInFinalSpan:=statement1.GetFirstToken(includeZeroWidth:=True), + lastTokenInFinalSpan:=statement2.GetLastToken(includeZeroWidth:=True)) + End Function - ' single statement case - With clone - .SelectionInExpression = TypeOf parent Is ExpressionSyntax - .SelectionInSingleStatement = TypeOf parent Is StatementSyntax - .FirstTokenInFinalSpan = parent.GetFirstToken() - .LastTokenInFinalSpan = parent.GetLastToken() - End With + Private Overloads Function GetInitialSelectionInfo(root As SyntaxNode) As SelectionInfo + Dim adjustedSpan = GetAdjustedSpan(root, Me.OriginalSpan) + Dim firstTokenInSelection = root.FindTokenOnRightOfPosition(adjustedSpan.Start, includeSkipped:=False) + Dim lastTokenInSelection = root.FindTokenOnLeftOfPosition(adjustedSpan.End, includeSkipped:=False) - Return clone + If firstTokenInSelection.Kind = SyntaxKind.None OrElse lastTokenInSelection.Kind = SyntaxKind.None Then + Return New SelectionInfo With {.Status = New OperationStatus(succeeded:=False, FeaturesResources.Invalid_selection), .OriginalSpan = adjustedSpan} End If - End If - With clone - .FirstTokenInFinalSpan = statement1.GetFirstToken(includeZeroWidth:=True) - .LastTokenInFinalSpan = statement2.GetLastToken(includeZeroWidth:=True) - End With - - Return clone - End Function + If firstTokenInSelection <> lastTokenInSelection AndAlso + firstTokenInSelection.Span.End > lastTokenInSelection.SpanStart Then + Return New SelectionInfo With {.Status = New OperationStatus(succeeded:=False, FeaturesResources.Invalid_selection), .OriginalSpan = adjustedSpan} + End If - Private Function GetInitialSelectionInfo(root As SyntaxNode) As SelectionInfo - Dim adjustedSpan = GetAdjustedSpan(root, Me.OriginalSpan) - Dim firstTokenInSelection = root.FindTokenOnRightOfPosition(adjustedSpan.Start, includeSkipped:=False) - Dim lastTokenInSelection = root.FindTokenOnLeftOfPosition(adjustedSpan.End, includeSkipped:=False) + If (Not adjustedSpan.Contains(firstTokenInSelection.Span)) AndAlso (Not adjustedSpan.Contains(lastTokenInSelection.Span)) Then + Return New SelectionInfo With + { + .Status = New OperationStatus(succeeded:=False, FeaturesResources.Selection_does_not_contain_a_valid_token), + .OriginalSpan = adjustedSpan, + .FirstTokenInOriginalSpan = firstTokenInSelection, + .LastTokenInOriginalSpan = lastTokenInSelection + } + End If - If firstTokenInSelection.Kind = SyntaxKind.None OrElse lastTokenInSelection.Kind = SyntaxKind.None Then - Return New SelectionInfo With {.Status = New OperationStatus(succeeded:=False, FeaturesResources.Invalid_selection), .OriginalSpan = adjustedSpan} - End If + If (Not firstTokenInSelection.UnderValidContext()) OrElse (Not lastTokenInSelection.UnderValidContext()) Then + Return New SelectionInfo With + { + .Status = New OperationStatus(succeeded:=False, FeaturesResources.No_valid_selection_to_perform_extraction), + .OriginalSpan = adjustedSpan, + .FirstTokenInOriginalSpan = firstTokenInSelection, + .LastTokenInOriginalSpan = lastTokenInSelection + } + End If - If firstTokenInSelection <> lastTokenInSelection AndAlso - firstTokenInSelection.Span.End > lastTokenInSelection.SpanStart Then - Return New SelectionInfo With {.Status = New OperationStatus(succeeded:=False, FeaturesResources.Invalid_selection), .OriginalSpan = adjustedSpan} - End If + Dim commonRoot = GetCommonRoot(firstTokenInSelection, lastTokenInSelection) + If commonRoot Is Nothing Then + Return New SelectionInfo With + { + .Status = New OperationStatus(succeeded:=False, FeaturesResources.No_common_root_node_for_extraction), + .OriginalSpan = adjustedSpan, + .FirstTokenInOriginalSpan = firstTokenInSelection, + .LastTokenInOriginalSpan = lastTokenInSelection + } + End If - If (Not adjustedSpan.Contains(firstTokenInSelection.Span)) AndAlso (Not adjustedSpan.Contains(lastTokenInSelection.Span)) Then - Return New SelectionInfo With - { - .Status = New OperationStatus(succeeded:=False, FeaturesResources.Selection_does_not_contain_a_valid_token), - .OriginalSpan = adjustedSpan, - .FirstTokenInOriginalSpan = firstTokenInSelection, - .LastTokenInOriginalSpan = lastTokenInSelection - } - End If + If Not commonRoot.ContainedInValidType() Then + Return New SelectionInfo With + { + .Status = New OperationStatus(succeeded:=False, FeaturesResources.Selection_not_contained_inside_a_type), + .OriginalSpan = adjustedSpan, + .FirstTokenInOriginalSpan = firstTokenInSelection, + .LastTokenInOriginalSpan = lastTokenInSelection + } + End If - If (Not firstTokenInSelection.UnderValidContext()) OrElse (Not lastTokenInSelection.UnderValidContext()) Then - Return New SelectionInfo With - { - .Status = New OperationStatus(succeeded:=False, FeaturesResources.No_valid_selection_to_perform_extraction), - .OriginalSpan = adjustedSpan, - .FirstTokenInOriginalSpan = firstTokenInSelection, - .LastTokenInOriginalSpan = lastTokenInSelection - } - End If + Dim selectionInExpression = TypeOf commonRoot Is ExpressionSyntax AndAlso + commonRoot.GetFirstToken(includeZeroWidth:=True) = firstTokenInSelection AndAlso + commonRoot.GetLastToken(includeZeroWidth:=True) = lastTokenInSelection + + If (Not selectionInExpression) AndAlso (Not commonRoot.UnderValidContext()) Then + Return New SelectionInfo With + { + .Status = New OperationStatus(succeeded:=False, FeaturesResources.No_valid_selection_to_perform_extraction), + .OriginalSpan = adjustedSpan, + .FirstTokenInOriginalSpan = firstTokenInSelection, + .LastTokenInOriginalSpan = lastTokenInSelection + } + End If - Dim commonRoot = GetCommonRoot(firstTokenInSelection, lastTokenInSelection) - If commonRoot Is Nothing Then - Return New SelectionInfo With - { - .Status = New OperationStatus(succeeded:=False, FeaturesResources.No_common_root_node_for_extraction), - .OriginalSpan = adjustedSpan, - .FirstTokenInOriginalSpan = firstTokenInSelection, - .LastTokenInOriginalSpan = lastTokenInSelection - } - End If + ' make sure type block enclosing the selection exist + If commonRoot.GetAncestor(Of TypeBlockSyntax)() Is Nothing Then + Return New SelectionInfo With + { + .Status = New OperationStatus(succeeded:=False, FeaturesResources.No_valid_selection_to_perform_extraction), + .OriginalSpan = adjustedSpan, + .FirstTokenInOriginalSpan = firstTokenInSelection, + .LastTokenInOriginalSpan = lastTokenInSelection + } + End If - If Not commonRoot.ContainedInValidType() Then - Return New SelectionInfo With - { - .Status = New OperationStatus(succeeded:=False, FeaturesResources.Selection_not_contained_inside_a_type), - .OriginalSpan = adjustedSpan, - .FirstTokenInOriginalSpan = firstTokenInSelection, - .LastTokenInOriginalSpan = lastTokenInSelection - } - End If - - Dim selectionInExpression = TypeOf commonRoot Is ExpressionSyntax AndAlso - commonRoot.GetFirstToken(includeZeroWidth:=True) = firstTokenInSelection AndAlso - commonRoot.GetLastToken(includeZeroWidth:=True) = lastTokenInSelection - - If (Not selectionInExpression) AndAlso (Not commonRoot.UnderValidContext()) Then Return New SelectionInfo With { - .Status = New OperationStatus(succeeded:=False, FeaturesResources.No_valid_selection_to_perform_extraction), + .Status = OperationStatus.SucceededStatus, .OriginalSpan = adjustedSpan, + .CommonRootFromOriginalSpan = commonRoot, + .SelectionInExpression = selectionInExpression, .FirstTokenInOriginalSpan = firstTokenInSelection, .LastTokenInOriginalSpan = lastTokenInSelection } - End If + End Function + + Public Overrides Function ContainsNonReturnExitPointsStatements(jumpsOutOfRegion As ImmutableArray(Of SyntaxNode)) As Boolean + Dim returnStatement = False + Dim exitStatement = False + + For Each statement In jumpsOutOfRegion + If TypeOf statement Is ReturnStatementSyntax Then + returnStatement = True + ElseIf TypeOf statement Is ExitStatementSyntax Then + exitStatement = True + Else + Return True + End If + Next - ' make sure type block enclosing the selection exist - If commonRoot.GetAncestor(Of TypeBlockSyntax)() Is Nothing Then - Return New SelectionInfo With - { - .Status = New OperationStatus(succeeded:=False, FeaturesResources.No_valid_selection_to_perform_extraction), - .OriginalSpan = adjustedSpan, - .FirstTokenInOriginalSpan = firstTokenInSelection, - .LastTokenInOriginalSpan = lastTokenInSelection - } - End If - - Return New SelectionInfo With - { - .Status = OperationStatus.SucceededStatus, - .OriginalSpan = adjustedSpan, - .CommonRootFromOriginalSpan = commonRoot, - .SelectionInExpression = selectionInExpression, - .FirstTokenInOriginalSpan = firstTokenInSelection, - .LastTokenInOriginalSpan = lastTokenInSelection - } - End Function - - Public Overrides Function ContainsNonReturnExitPointsStatements(jumpsOutOfRegion As IEnumerable(Of SyntaxNode)) As Boolean - Dim returnStatement = False - Dim exitStatement = False - - For Each statement In jumpsOutOfRegion - If TypeOf statement Is ReturnStatementSyntax Then - returnStatement = True - ElseIf TypeOf statement Is ExitStatementSyntax Then - exitStatement = True - Else - Return True + If exitStatement Then + Return Not returnStatement End If - Next - If exitStatement Then - Return Not returnStatement - End If + Return False + End Function - Return False - End Function + Public Overrides Function GetOuterReturnStatements(commonRoot As SyntaxNode, jumpsOutOfRegionStatements As ImmutableArray(Of SyntaxNode)) As ImmutableArray(Of ExecutableStatementSyntax) + Dim container = commonRoot.GetAncestorsOrThis(Of SyntaxNode)().Where(Function(a) a.IsReturnableConstruct()).FirstOrDefault() + If container Is Nothing Then + Return ImmutableArray(Of ExecutableStatementSyntax).Empty + End If - Public Overrides Function GetOuterReturnStatements(commonRoot As SyntaxNode, jumpsOutOfRegionStatements As IEnumerable(Of SyntaxNode)) As IEnumerable(Of SyntaxNode) - Dim returnStatements = jumpsOutOfRegionStatements.Where(Function(n) TypeOf n Is ReturnStatementSyntax OrElse TypeOf n Is ExitStatementSyntax) + ' now filter return statements to only include the one under outmost container + Return jumpsOutOfRegionStatements. + OfType(Of ExecutableStatementSyntax). + Where(Function(n) TypeOf n Is ReturnStatementSyntax OrElse TypeOf n Is ExitStatementSyntax). + Select(Function(returnStatement) (returnStatement, container:=returnStatement.GetAncestors(Of SyntaxNode)().Where(Function(a) a.IsReturnableConstruct()).FirstOrDefault())). + Where(Function(p) p.container Is container). + SelectAsArray(Function(p) p.returnStatement) + End Function + + Public Overrides Function IsFinalSpanSemanticallyValidSpan( + textSpan As TextSpan, + returnStatements As ImmutableArray(Of ExecutableStatementSyntax), + cancellationToken As CancellationToken) As Boolean + + ' do quick check to make sure we are under sub (no return value) container. otherwise, there is no point to anymore checks. + If returnStatements.Any(Function(s) + Return s.TypeSwitch( + Function(e As ExitStatementSyntax) e.BlockKeyword.Kind <> SyntaxKind.SubKeyword, + Function(r As ReturnStatementSyntax) r.Expression IsNot Nothing, + Function(n As ExecutableStatementSyntax) True) + End Function) Then + Return False + End If - Dim container = commonRoot.GetAncestorsOrThis(Of SyntaxNode)().Where(Function(a) a.IsReturnableConstruct()).FirstOrDefault() - If container Is Nothing Then - Return SpecializedCollections.EmptyEnumerable(Of SyntaxNode)() - End If + ' check whether selection reaches the end of the container + Dim lastToken = Me.SemanticDocument.Root.FindToken(textSpan.End) + If lastToken.Kind = SyntaxKind.None Then + Return False + End If - Dim returnableConstructPairs = returnStatements. - Select(Function(r) (r, r.GetAncestors(Of SyntaxNode)().Where(Function(a) a.IsReturnableConstruct()).FirstOrDefault())). - Where(Function(p) p.Item2 IsNot Nothing) + Dim nextToken = lastToken.GetNextToken(includeZeroWidth:=True) - ' now filter return statements to only include the one under outmost container - Return returnableConstructPairs.Where(Function(p) p.Item2 Is container).Select(Function(p) p.Item1) - End Function + Dim container = nextToken.GetAncestors(Of SyntaxNode).Where(Function(n) n.IsReturnableConstruct()).FirstOrDefault() + If container Is Nothing Then + Return False + End If - Public Overrides Function IsFinalSpanSemanticallyValidSpan(root As SyntaxNode, - textSpan As TextSpan, - returnStatements As IEnumerable(Of SyntaxNode), - cancellationToken As CancellationToken) As Boolean + Dim match = If(TryCast(container, MethodBlockBaseSyntax)?.EndBlockStatement.EndKeyword = nextToken, False) OrElse + If(TryCast(container, MultiLineLambdaExpressionSyntax)?.EndSubOrFunctionStatement.EndKeyword = nextToken, False) - ' do quick check to make sure we are under sub (no return value) container. otherwise, there is no point to anymore checks. - If returnStatements.Any(Function(s) - Return s.TypeSwitch( - Function(e As ExitStatementSyntax) e.BlockKeyword.Kind <> SyntaxKind.SubKeyword, - Function(r As ReturnStatementSyntax) r.Expression IsNot Nothing, - Function(n As SyntaxNode) True) - End Function) Then - Return False - End If + If Not match Then + Return False + End If - ' check whether selection reaches the end of the container - Dim lastToken = root.FindToken(textSpan.End) - If lastToken.Kind = SyntaxKind.None Then - Return False - End If + If TryCast(container, MethodBlockBaseSyntax)?.BlockStatement.Kind = SyntaxKind.SubStatement Then + Return True + ElseIf TryCast(container, MultiLineLambdaExpressionSyntax)?.SubOrFunctionHeader.Kind = SyntaxKind.SubLambdaHeader Then + Return True + Else + Return False + End If + End Function - Dim nextToken = lastToken.GetNextToken(includeZeroWidth:=True) + Private Shared Function GetAdjustedSpan(root As SyntaxNode, textSpan As TextSpan) As TextSpan + ' quick exit + If textSpan.IsEmpty OrElse textSpan.End = 0 Then + Return textSpan + End If - Dim container = nextToken.GetAncestors(Of SyntaxNode).Where(Function(n) n.IsReturnableConstruct()).FirstOrDefault() - If container Is Nothing Then - Return False - End If + ' regular column 0 check + Dim line = root.GetText().Lines.GetLineFromPosition(textSpan.End) + If line.Start <> textSpan.End Then + Return textSpan + End If - Dim match = If(TryCast(container, MethodBlockBaseSyntax)?.EndBlockStatement.EndKeyword = nextToken, False) OrElse - If(TryCast(container, MultiLineLambdaExpressionSyntax)?.EndSubOrFunctionStatement.EndKeyword = nextToken, False) + ' previous line + Contract.ThrowIfFalse(line.LineNumber > 0) + Dim previousLine = root.GetText().Lines(line.LineNumber - 1) - If Not match Then - Return False - End If + ' check whether end of previous line is last token of a statement. if it is, don't do anything + If root.FindTokenOnLeftOfPosition(previousLine.End).IsLastTokenOfStatement() Then + Return textSpan + End If - If TryCast(container, MethodBlockBaseSyntax)?.BlockStatement.Kind = SyntaxKind.SubStatement Then - Return True - ElseIf TryCast(container, MultiLineLambdaExpressionSyntax)?.SubOrFunctionHeader.Kind = SyntaxKind.SubLambdaHeader Then - Return True - Else - Return False - End If - End Function - - Private Shared Function GetAdjustedSpan(root As SyntaxNode, textSpan As TextSpan) As TextSpan - ' quick exit - If textSpan.IsEmpty OrElse textSpan.End = 0 Then - Return textSpan - End If - - ' regular column 0 check - Dim line = root.GetText().Lines.GetLineFromPosition(textSpan.End) - If line.Start <> textSpan.End Then - Return textSpan - End If - - ' previous line - Contract.ThrowIfFalse(line.LineNumber > 0) - Dim previousLine = root.GetText().Lines(line.LineNumber - 1) - - ' check whether end of previous line is last token of a statement. if it is, don't do anything - If root.FindTokenOnLeftOfPosition(previousLine.End).IsLastTokenOfStatement() Then - Return textSpan - End If - - ' move end position of the selection - Return TextSpan.FromBounds(textSpan.Start, previousLine.End) - End Function + ' move end position of the selection + Return TextSpan.FromBounds(textSpan.Start, previousLine.End) + End Function + End Class End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/VBFeaturesResources.resx b/src/Features/VisualBasic/Portable/VBFeaturesResources.resx index 58d9b8826c0dc..50f0be21cee17 100644 --- a/src/Features/VisualBasic/Portable/VBFeaturesResources.resx +++ b/src/Features/VisualBasic/Portable/VBFeaturesResources.resx @@ -214,9 +214,6 @@ Remove 'Me' qualification {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - contains invalid selection @@ -250,9 +247,6 @@ Selection doesn't contain any valid node - - no valid statement range to extract out - Deprecated diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf index 791b434256c70..b5df205f28f71 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf @@ -222,11 +222,6 @@ Odebrat kvalifikaci Me {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - nemůže určit platný rozsah příkazů k extrakci ven - - contains invalid selection obsahuje neplatný výběr @@ -282,11 +277,6 @@ Výběr nemůže obsahovat žádný platný uzel. - - no valid statement range to extract out - žádný platný rozsah příkazů pro extrakci ven - - Deprecated Zastaralé diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf index 24330c0bc8293..86fbb06a50748 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf @@ -222,11 +222,6 @@ Qualifikation "Me" entfernen {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - gültiger Bereich der zu extrahierenden Anweisungen kann nicht bestimmt werden - - contains invalid selection enthält ungültige Auswahl @@ -282,11 +277,6 @@ Auswahl enthält keinen gültigen Knoten - - no valid statement range to extract out - kein gültiger Anweisungsbereich für die Extraktion - - Deprecated Veraltet diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf index cda2e71d133f7..aed9c60cf3e0c 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf @@ -222,11 +222,6 @@ Quitar calificación "Me" {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - no se puede determinar el intervalo válido de instrucciones para extraer - - contains invalid selection contiene una selección no válida @@ -282,11 +277,6 @@ Selection no contiene ningún nodo válido - - no valid statement range to extract out - ningún intervalo de instrucciones válido para extraer - - Deprecated En desuso diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf index 358ad469cdda4..333f6532e8ff0 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf @@ -222,11 +222,6 @@ Supprimer la qualification 'Me' {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - impossible de déterminer la plage valide d'instructions à extraire - - contains invalid selection contient une sélection non valide @@ -282,11 +277,6 @@ La sélection ne contient aucun nœud valide - - no valid statement range to extract out - Aucune plage d'instructions valides à extraire - - Deprecated Déconseillé diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf index 07ae1d4317553..d7dd34f055537 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf @@ -222,11 +222,6 @@ Rimuovi qualificazione 'Me' {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - non è possibile determinare l'intervallo valido di istruzioni da estrarre - - contains invalid selection contiene una selezione non valida @@ -282,11 +277,6 @@ La selezione non contiene nodi validi - - no valid statement range to extract out - l'intervallo di istruzioni non è valido per l'estrazione - - Deprecated Deprecato diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf index 81320a04d9f85..2f36b3492eeba 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf @@ -222,11 +222,6 @@ 修飾子 'Me' を削除します {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - 抽出するステートメントの有効な範囲を決定できません - - contains invalid selection 無効な選択が含まれています @@ -282,11 +277,6 @@ 選択範囲に有効なノードが含まれていません - - no valid statement range to extract out - 抽出する有効なステートメントの範囲がありません - - Deprecated 非推奨 diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf index 1f15efec97096..4356c4cefa873 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf @@ -222,11 +222,6 @@ Me' 한정자 제거 {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - 추출할 문에 유효한 범위를 결정할 수 없습니다. - - contains invalid selection 선택 영역이 잘못되었습니다. @@ -282,11 +277,6 @@ 선택 영역에 유효한 노드가 포함되어 있지 않습니다. - - no valid statement range to extract out - 추출하는 데 유효한 문 범위가 없습니다. - - Deprecated 사용되지 않음 diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf index 47dd8169ca227..adba0504e637f 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf @@ -222,11 +222,6 @@ Usuń kwalifikację „Me” {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - nie można określić prawidłowego zakresu instrukcji do wyodrębnienia - - contains invalid selection zawiera nieprawidłowe zaznaczenie @@ -282,11 +277,6 @@ Zaznaczenie nie zawiera prawidłowych węzłów - - no valid statement range to extract out - brak prawidłowego zakresu instrukcji do wyodrębnienia - - Deprecated Przestarzały diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf index e1d6a3a97614f..3db1a8c844ace 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf @@ -222,11 +222,6 @@ Remover qualificação 'Me' {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - não pode determinar o intervalo válido de instruções para extrair - - contains invalid selection contém seleção inválida @@ -282,11 +277,6 @@ A seleção não contém qualquer nó válido - - no valid statement range to extract out - nenhum intervalo de instrução válido para extrair - - Deprecated Preterido diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf index dde37b3fbaf1f..b49b20063f50b 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf @@ -222,11 +222,6 @@ Удаление квалификации Me {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - Невозможно определить допустимый диапазон операторов для извлечения. - - contains invalid selection содержит недопустимый выделенный фрагмент @@ -282,11 +277,6 @@ Выделенный фрагмент не содержит допустимый узел - - no valid statement range to extract out - Отсутствует допустимый диапазон оператора для извлечения. - - Deprecated Нерекомендуемый diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf index c08dd8e49737a..33125a1f21824 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf @@ -222,11 +222,6 @@ Me' nitelemesini kaldır {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - Dışarıya ayıklanacak deyimlerin geçerli aralığı belirlenemiyor - - contains invalid selection Geçersiz seçim içeriyor @@ -282,11 +277,6 @@ Seçim geçerli düğüm içermiyor - - no valid statement range to extract out - Dışarıya ayıklanacak geçerli deyim aralığı yok - - Deprecated Kullanım Dışı diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf index 1cc869b98bfa5..760776b6c6499 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf @@ -222,11 +222,6 @@ 删除 "Me" 资格 {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - 无法确定要提取的语句的有效范围 - - contains invalid selection 包含无效的选择 @@ -282,11 +277,6 @@ 所选内容不包含任何有效的节点 - - no valid statement range to extract out - 没有可供提取的有效语句范围 - - Deprecated 已弃用 diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf index bcf7fff5b1468..3b9b0bdfbc8c4 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf @@ -222,11 +222,6 @@ 移除 'Me' 限定性條件 {Locked="Me"} "Me" is a VB keyword and should not be localized. - - can't determine valid range of statements to extract out - 無法決定要擷取的有效陳述式範圍 - - contains invalid selection 包含無效的選取範圍 @@ -282,11 +277,6 @@ 選取範圍不包含任何有效的節點 - - no valid statement range to extract out - 沒有可擷取內容的有效陳述式範圍 - - Deprecated 取代 diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index b3d726020db00..bad510b09163a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -238,25 +238,6 @@ public bool IsGlobalStatement([NotNullWhen(true)] SyntaxNode? node) public SyntaxNode GetStatementOfGlobalStatement(SyntaxNode node) => ((GlobalStatementSyntax)node).Statement; - public bool AreStatementsInSameContainer(SyntaxNode firstStatement, SyntaxNode secondStatement) - { - Debug.Assert(IsStatement(firstStatement)); - Debug.Assert(IsStatement(secondStatement)); - - if (firstStatement.Parent == secondStatement.Parent) - return true; - - if (IsGlobalStatement(firstStatement.Parent) - && IsGlobalStatement(secondStatement.Parent) - && firstStatement.Parent.Parent == secondStatement.Parent.Parent) - { - return true; - } - - return false; - - } - public bool IsMethodBody([NotNullWhen(true)] SyntaxNode? node) { if (node is BlockSyntax or diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenExtensions.cs index da3d4f565326d..0408f09cefafa 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenExtensions.cs @@ -36,19 +36,8 @@ public static IEnumerable GetAncestors(this SyntaxToken token, Func< : []; } - public static SyntaxNode? GetCommonRoot(this SyntaxToken token1, SyntaxToken token2) - { - Contract.ThrowIfTrue(token1.RawKind == 0 || token2.RawKind == 0); - - // find common starting node from two tokens. - // as long as two tokens belong to same tree, there must be at least on common root (Ex, compilation unit) - if (token1.Parent == null || token2.Parent == null) - { - return null; - } - - return token1.Parent.GetCommonRoot(token2.Parent); - } + public static SyntaxNode GetCommonRoot(this SyntaxToken token1, SyntaxToken token2) + => token1.GetRequiredParent().GetCommonRoot(token2.GetRequiredParent()); public static bool CheckParent(this SyntaxToken token, Func valueChecker) where T : SyntaxNode { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index feac9babc2d0c..511e68649d0fe 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -336,7 +336,6 @@ void GetPartsOfTupleExpression(SyntaxNode node, bool IsExecutableStatement([NotNullWhen(true)] SyntaxNode? node); bool IsGlobalStatement([NotNullWhen(true)] SyntaxNode? node); SyntaxNode GetStatementOfGlobalStatement(SyntaxNode node); - bool AreStatementsInSameContainer(SyntaxNode firstStatement, SyntaxNode secondStatement); bool IsDeconstructionAssignment([NotNullWhen(true)] SyntaxNode? node); bool IsDeconstructionForEachStatement([NotNullWhen(true)] SyntaxNode? node); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index 17d502a2e85bc..362500edd226b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -225,13 +225,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService Throw New InvalidOperationException(DoesNotExistInVBErrorMessage) End Function - Public Function AreStatementsInSameContainer(firstStatement As SyntaxNode, secondStatement As SyntaxNode) As Boolean Implements ISyntaxFacts.AreStatementsInSameContainer - Debug.Assert(IsStatement(firstStatement)) - Debug.Assert(IsStatement(secondStatement)) - - Return firstStatement.Parent Is secondStatement.Parent - End Function - Public Function IsMethodBody(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsMethodBody Return TypeOf node Is MethodBlockBaseSyntax End Function diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs index 48dbaa6e29eb5..9bea07ded436a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs @@ -68,7 +68,7 @@ internal static ParameterSyntax GetParameter(IParameterSymbol parameter, CSharpC return Parameter(parameter.Name.ToIdentifierToken()) .WithAttributeLists(GenerateAttributes(parameter, isExplicit, info)) .WithModifiers(GenerateModifiers(parameter, isFirstParam)) - .WithType(parameter.Type.GenerateTypeSyntax()) + .WithType(parameter.Type.GenerateTypeSyntax(allowVar: false)) .WithDefault(GenerateEqualsValueClause(info.Generator, parameter, isExplicit, seenOptional)); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs index 5ad944b1633c9..b9610a98721e0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs @@ -72,9 +72,9 @@ private static TypeSyntax GenerateTypeSyntax( } public static TypeSyntax GenerateRefTypeSyntax( - this INamespaceOrTypeSymbol symbol) + this INamespaceOrTypeSymbol symbol, bool allowVar = true) { - var underlyingType = GenerateTypeSyntax(symbol) + var underlyingType = GenerateTypeSyntax(symbol, allowVar) .WithPrependedLeadingTrivia(ElasticMarker) .WithAdditionalAnnotations(Simplifier.Annotation); var refKeyword = RefKeyword; @@ -82,9 +82,9 @@ public static TypeSyntax GenerateRefTypeSyntax( } public static TypeSyntax GenerateRefReadOnlyTypeSyntax( - this INamespaceOrTypeSymbol symbol) + this INamespaceOrTypeSymbol symbol, bool allowVar = true) { - var underlyingType = GenerateTypeSyntax(symbol) + var underlyingType = GenerateTypeSyntax(symbol, allowVar) .WithPrependedLeadingTrivia(ElasticMarker) .WithAdditionalAnnotations(Simplifier.Annotation); var refKeyword = RefKeyword; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeSyntaxExtensions.cs index 9de84333a9b8b..86742c57226e1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeSyntaxExtensions.cs @@ -58,15 +58,15 @@ public static TypeSyntax GenerateReturnTypeSyntax(this IMethodSymbol method) if (method.ReturnsByRef) { - return returnType.GenerateRefTypeSyntax(); + return returnType.GenerateRefTypeSyntax(allowVar: false); } else if (method.ReturnsByRefReadonly) { - return returnType.GenerateRefReadOnlyTypeSyntax(); + return returnType.GenerateRefReadOnlyTypeSyntax(allowVar: false); } else { - return returnType.GenerateTypeSyntax(); + return returnType.GenerateTypeSyntax(allowVar: false); } } }