-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update Extract Method to support top-level statements #44453
Conversation
var insertionIndex = compilationUnit.Members.LastIndexOf(memberDeclaration => memberDeclaration.IsKind(SyntaxKind.GlobalStatement)) + 1; | ||
return Cast<TDeclarationNode>(compilationUnit.WithMembers(compilationUnit.Members.InsertRange(insertionIndex, StatementGenerator.GenerateStatements(statements).Select(generated => SyntaxFactory.GlobalStatement(generated)).ToArray()))); | ||
} | ||
else if (destinationMember is StatementSyntax statement && statement.IsParentKind(SyntaxKind.GlobalStatement)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this block i don't quite understand. what's the case where the destinatino member is a statement itself? who is adding statements to a statement itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Top-level statements of a script are not part of a statement container. In this case, we have to create the container and move the original statement into it along with the new statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you give a concrete example of the type of statement we are adding, and the statement it is being added to? it's not clear to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behavior supports the following test:
roslyn/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractLocalFunctionTests.cs
Lines 4790 to 4808 in 6573bd1
[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""|]); | |
"; | |
var expected = | |
@"{ | |
System.Console.WriteLine({|Rename:NewMethod|}()); | |
static string NewMethod() | |
{ | |
return ""string""; | |
} | |
}"; | |
await TestAsync(code, expected, TestOptions.Script.WithLanguageVersion(LanguageVersionExtensions.CSharp9), index: CodeActionIndex); | |
} |
If we didn't wrap the generated code in a block, the generated function would be a method declaration as opposed to a local function declaration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to disable extract-local-function here instead? this really doesn't seem like a desirable change to me. Plus, the addition of the block seems like it can actually screw things up. For example, what if we had var v = [|1 + 1|];
. Would we make a block that only contained the variable and the local-func, causing references to 'v' to not work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would make sense to disable, but that was a bit more complicated and only impacts script scenarios. I'll file a follow-up issue for it.
df672de
to
6573bd1
Compare
6573bd1
to
99397da
Compare
@CyrusNajmabadi for review |
}"; | ||
} | ||
|
||
local = NewMethod(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this ordering change? Should we offer 2 fixes here: 1. to insert in-place and 2. to insert at end of the current block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scenario is only valid for C# scripting, so I'm not sure it matters. It would be covered by https://github.com/dotnet/roslyn/pull/44453/files#r428859532.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's going to be much easier to deal with these details when the tests are converted to the new test library.
@CyrusNajmabadi let me know if you think this one is good to merge |
@@ -482,6 +482,21 @@ protected override TDeclarationNode AddMembers<TDeclarationNode>(TDeclarationNod | |||
{ | |||
return (accessorDeclaration.Body == null) ? destinationMember : Cast<TDeclarationNode>(accessorDeclaration.AddBodyStatements(StatementGenerator.GenerateStatements(statements).ToArray())); | |||
} | |||
else if (destinationMember is CompilationUnitSyntax compilationUnit && options is null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the check for options is null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method had an existing caller that passed in CompilationUnitSyntax
with options, and relied on the AddStatementsWorker
fallback to handle those options. Right now I forget what caller that was.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
➡️ Will submit a follow-up to clarify the condition.
else if (destinationMember is CompilationUnitSyntax compilationUnit && options is null) | ||
{ | ||
// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would love it we doc'ed that LastIndexOf returns -1 on failure. that would make me feel much safer about this code :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
➡️ Will submit a follow-up to clarify.
{ | ||
// 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.ToArray seems unnecessary. though it looks like there's a lot of that in this mtehod :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partially pattern matching with the rest of the method, but also easier to deal with when reading and debugging through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall i'm ok with this. A couple of tiny clarity questions. but they're not blocking (unless hte answers are very bizarre :))
Fixes #44260