Skip to content
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

Extract method cleanup #76550

Merged
merged 56 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e420844
Cleanup extract method
CyrusNajmabadi Dec 21, 2024
9fd5942
Switch to records
CyrusNajmabadi Dec 21, 2024
3d4972d
Use with method
CyrusNajmabadi Dec 21, 2024
2ad448c
Simplify
CyrusNajmabadi Dec 21, 2024
7498849
Delete
CyrusNajmabadi Dec 21, 2024
a268671
Make code non-generic
CyrusNajmabadi Dec 21, 2024
4279e02
Make code non-generic
CyrusNajmabadi Dec 21, 2024
66392a3
Remove syntax fact
CyrusNajmabadi Dec 21, 2024
76a2126
Switch to an enum
CyrusNajmabadi Dec 21, 2024
c89ca66
Move code inwards
CyrusNajmabadi Dec 21, 2024
bba902d
Share resource
CyrusNajmabadi Dec 21, 2024
7812d84
in progress
CyrusNajmabadi Dec 21, 2024
96eb133
Share code
CyrusNajmabadi Dec 21, 2024
db593e9
Fixup tests
CyrusNajmabadi Dec 21, 2024
c9a9089
In progress
CyrusNajmabadi Dec 21, 2024
8667d52
Share code
CyrusNajmabadi Dec 21, 2024
3008da4
NRT
CyrusNajmabadi Dec 21, 2024
d80f03d
In progress
CyrusNajmabadi Dec 21, 2024
c93e5ae
Share code
CyrusNajmabadi Dec 21, 2024
bf2878d
Share code
CyrusNajmabadi Dec 21, 2024
950c2fc
Simplify
CyrusNajmabadi Dec 21, 2024
e92a3e0
NRT
CyrusNajmabadi Dec 21, 2024
7cc9a50
Merge branch 'extractMethodFocus' into extractMethodCleanup
CyrusNajmabadi Dec 21, 2024
f17b48d
Move under common types to share generics
CyrusNajmabadi Dec 21, 2024
4b5bd3c
Share code
CyrusNajmabadi Dec 21, 2024
fc6bbec
Stronger types
CyrusNajmabadi Dec 21, 2024
e4a9ee1
Update src/Features/Core/Portable/ExtractMethod/AbstractExtractMethod…
CyrusNajmabadi Dec 22, 2024
9ebfa1f
NRT
CyrusNajmabadi Dec 22, 2024
9ab4590
Merge branch 'extractMethodCleanup' of https://github.com/CyrusNajmab…
CyrusNajmabadi Dec 22, 2024
4494693
NRT
CyrusNajmabadi Dec 22, 2024
dd3e177
Move enum
CyrusNajmabadi Dec 22, 2024
168bd3b
Share resource string
CyrusNajmabadi Dec 22, 2024
5c62de5
VB statement types
CyrusNajmabadi Dec 22, 2024
8522cb1
Docs
CyrusNajmabadi Dec 22, 2024
a24134d
strong typing
CyrusNajmabadi Dec 22, 2024
6df7847
strong typing
CyrusNajmabadi Dec 22, 2024
c962711
Simplify
CyrusNajmabadi Dec 22, 2024
3225e22
Make sealed
CyrusNajmabadi Dec 25, 2024
70a0fde
make non optional
CyrusNajmabadi Dec 25, 2024
6830b2f
make into records
CyrusNajmabadi Dec 25, 2024
8bf7cd6
pull out computation
CyrusNajmabadi Dec 25, 2024
3e3fea0
Change how anonymous types are handled
CyrusNajmabadi Dec 25, 2024
73218cf
Make static
CyrusNajmabadi Dec 25, 2024
7987c9a
Use syntax kinds
CyrusNajmabadi Dec 25, 2024
ef0568f
Pass arrays
CyrusNajmabadi Dec 25, 2024
8d0afed
Do not pass semantic model
CyrusNajmabadi Dec 25, 2024
171b3d3
Anon types
CyrusNajmabadi Dec 25, 2024
93a3846
Cleanup
CyrusNajmabadi Dec 25, 2024
6a1c3f3
Do not pass semantic model along
CyrusNajmabadi Dec 25, 2024
d5fcdeb
Clean up return types
CyrusNajmabadi Dec 25, 2024
39bfda0
Clean up return types
CyrusNajmabadi Dec 25, 2024
8bdf297
Simplufy
CyrusNajmabadi Dec 26, 2024
07aeb41
Inline
CyrusNajmabadi Dec 26, 2024
acfbf81
Move helper
CyrusNajmabadi Dec 29, 2024
9304403
Merge branch 'main' into extractMethodCleanup
CyrusNajmabadi Jan 2, 2025
2038e1a
Merge branch 'main' into extractMethodCleanup
CyrusNajmabadi Jan 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -128,19 +129,8 @@ protected static async Task<SyntaxNode> 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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intentional change. EM now calls trhough the correct syntax generator code. but the tests don't actually run the simplification pass (since the tests unfortunately test parts of the api directly).

{
return p2;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -172,7 +172,7 @@ End Class</text>

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
Expand Down
6 changes: 0 additions & 6 deletions src/Features/CSharp/Portable/CSharpFeaturesResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,6 @@
<data name="Remove_this_qualification" xml:space="preserve">
<value>Remove 'this' qualification</value>
</data>
<data name="Cannot_determine_valid_range_of_statements_to_extract" xml:space="preserve">
<value>Cannot determine valid range of statements to extract</value>
</data>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to shared location so we can have one string for VB and C#

<data name="Selection_does_not_contain_a_valid_node" xml:space="preserve">
<value>Selection does not contain a valid node</value>
</data>
Expand Down Expand Up @@ -181,9 +178,6 @@
<data name="The_selected_code_is_inside_an_unsafe_context" xml:space="preserve">
<value>The selected code is inside an unsafe context.</value>
</data>
<data name="No_valid_statement_range_to_extract" xml:space="preserve">
<value>No valid statement range to extract</value>
</data>
<data name="deprecated" xml:space="preserve">
<value>deprecated</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core helper types became nested types of the service. this way they do not have to repeat type arguments.

StatementSyntax,
StatementSyntax,
ExpressionSyntax>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,86 +3,88 @@
// 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;
using Microsoft.CodeAnalysis.ExtractMethod;

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<int> 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<PrimaryConstructorBaseTypeSyntax>() != null;
protected override bool IsInPrimaryConstructorBaseType()
=> this.SelectionResult.GetContainingScopeOf<PrimaryConstructorBaseTypeSyntax>() != null;

protected override VariableInfo CreateFromSymbol(
ISymbol symbol, ITypeSymbol type, VariableStyle style, bool variableDeclared)
{
return CreateFromSymbolCommon<LocalDeclarationStatementSyntax>(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<SyntaxNode> jumpOutOfRegionStatements)
=> jumpOutOfRegionStatements.Where(n => n is ReturnStatementSyntax).Any();
protected override bool ContainsReturnStatementInSelectedCode(ImmutableArray<SyntaxNode> exitPoints)
=> exitPoints.Any(n => n is ReturnStatementSyntax);

protected override bool ReadOnlyFieldAllowed()
{
var scope = SelectionResult.GetContainingScopeOf<ConstructorDeclarationSyntax>();
return scope == null;
}
protected override bool ReadOnlyFieldAllowed()
{
var scope = SelectionResult.GetContainingScopeOf<ConstructorDeclarationSyntax>();
return scope == null;
}

protected override bool IsReadOutside(ISymbol symbol, HashSet<ISymbol> readOutsideMap)
{
if (!base.IsReadOutside(symbol, readOutsideMap))
return false;
protected override bool IsReadOutside(ISymbol symbol, HashSet<ISymbol> 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 &&
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed this property for consistency, and moved to an enum to fully specify it.

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;
}
}
}
}
Loading
Loading