Skip to content

Commit

Permalink
Update Extract Method to support top-level statements
Browse files Browse the repository at this point in the history
Fixes #44260
  • Loading branch information
sharwell committed May 21, 2020
1 parent 0b043dd commit 6573bd1
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
Expand Down Expand Up @@ -4749,14 +4750,61 @@ void NewMethod()
[Fact, Trait(Traits.Feature, Traits.Features.ExtractMethod)]
public async Task TopLevelStatement_ArgumentInInvocation()
{
// ExtractLocalFunction currently disabled in the presence of top-level statements
// https://github.com/dotnet/roslyn/issues/44260
var code = @"
System.Console.WriteLine([|""string""|]);
";
var expected = @"
System.Console.WriteLine({|Rename:NewMethod|}());
static string NewMethod()
{
return ""string"";
}";

await TestAsync(code, expected, TestOptions.Regular.WithLanguageVersion(LanguageVersionExtensions.CSharp9), index: CodeActionIndex);
}

[WorkItem(44260, "https://github.com/dotnet/roslyn/issues/44260")]
[Fact, Trait(Traits.Feature, Traits.Features.ExtractMethod)]
public async Task TopLevelStatement_InBlock_ArgumentInInvocation()
{
var code = @"
{
System.Console.WriteLine([|""string""|]);
}
";
var expected = @"
{
System.Console.WriteLine({|Rename:NewMethod|}());
static string NewMethod()
{
return ""string"";
}
}
";

await TestInRegularAndScriptAsync(code, expected, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersionExtensions.CSharp9), index: CodeActionIndex);
}

[WorkItem(44260, "https://github.com/dotnet/roslyn/issues/44260")]
[Fact, Trait(Traits.Feature, Traits.Features.ExtractMethod)]
public async Task TopLevelStatement_ArgumentInInvocation_InInteractive()
{
var code = @"
System.Console.WriteLine([|""string""|]);
";
await TestExactActionSetOfferedAsync(code, new[] { FeaturesResources.Extract_method },
new TestParameters(parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp8)));
var expected =
@"{
System.Console.WriteLine({|Rename:NewMethod|}());
static string NewMethod()
{
return ""string"";
}
}";

await TestAsync(code, expected, TestOptions.Script.WithLanguageVersion(LanguageVersionExtensions.CSharp9), index: CodeActionIndex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ExtractMethod
Expand Down Expand Up @@ -103,7 +104,7 @@ protected async Task TestExtractMethodAsync(
{
if (expected != "")
{
Assert.Equal(expected, actual);
AssertEx.EqualOrDiff(expected, actual);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11196,12 +11196,14 @@ public async Task TopLevelStatement_ValueInAssignment()
";
var expected = @"
bool local;
local = NewMethod();
bool NewMethod()
{
return true;
}";
}
local = NewMethod();
";
await TestExtractMethodAsync(code, expected);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.LanguageServices;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Formatting;
Expand Down Expand Up @@ -69,6 +70,13 @@ protected override async Task<InsertionPoint> GetInsertionPointAsync(SemanticDoc
return await InsertionPoint.CreateAsync(document, globalStatement.Parent, cancellationToken).ConfigureAwait(false);
}

// 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 await InsertionPoint.CreateAsync(document, globalStatement.Parent, cancellationToken).ConfigureAwait(false);
}

return await InsertionPoint.CreateAsync(document, globalStatement.Statement, cancellationToken).ConfigureAwait(false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.ExtractMethod
Expand Down Expand Up @@ -38,15 +36,6 @@ public async Task<ExtractMethodResult> ExtractMethodAsync(
return new FailedExtractMethodResult(selectionResult.Status);
}

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
if (localFunction && syntaxFacts.ContainsGlobalStatement(root))
{
// ExtractLocalFunction doesn't yet support local functions in top-level statements
// https://github.com/dotnet/roslyn/issues/44260
return new FailedExtractMethodResult(OperationStatus.FailedWithUnknownReason);
}

cancellationToken.ThrowIfCancellationRequested();

// extract method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServices;
Expand Down Expand Up @@ -482,6 +483,21 @@ public override TDeclarationNode AddStatements<TDeclarationNode>(
{
return (accessorDeclaration.Body == null) ? destinationMember : Cast<TDeclarationNode>(accessorDeclaration.AddBodyStatements(StatementGenerator.GenerateStatements(statements).ToArray()));
}
else if (destinationMember is CompilationUnitSyntax compilationUnit)
{
// Insert the new global statement(s) at the end of any current global statements
var insertionIndex = compilationUnit.Members.LastIndexOf(memberDeclaration => memberDeclaration.IsKind(SyntaxKind.GlobalStatement)) + 1;
var wrappedStatements = StatementGenerator.GenerateStatements(statements).Select(generated => SyntaxFactory.GlobalStatement(generated)).ToArray();
return Cast<TDeclarationNode>(compilationUnit.WithMembers(compilationUnit.Members.InsertRange(insertionIndex, wrappedStatements)));
}
else if (destinationMember is StatementSyntax statement && statement.IsParentKind(SyntaxKind.GlobalStatement))
{
// We are adding a statement to a global statement in script, where the CompilationUnitSyntax is not a
// statement container. If the global statement is not already a block, create a block which can hold
// both the original statement and any new statements we are adding to it.
var block = statement as BlockSyntax ?? SyntaxFactory.Block(statement);
return Cast<TDeclarationNode>(block.AddStatements(StatementGenerator.GenerateStatements(statements).ToArray()));
}
else
{
return AddStatementsWorker(destinationMember, statements, options, cancellationToken);
Expand Down

0 comments on commit 6573bd1

Please sign in to comment.