Skip to content

Commit

Permalink
Update 'introduce using' to support expression statements (#76147)
Browse files Browse the repository at this point in the history
Fixes #37260
  • Loading branch information
CyrusNajmabadi authored Dec 2, 2024
2 parents c4598d2 + 654c0b3 commit 38b8e68
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.IntroduceUsingStatement;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
Expand All @@ -18,17 +19,20 @@ namespace Microsoft.CodeAnalysis.CSharp.IntroduceUsingStatement;

[ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.IntroduceVariable)]
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.IntroduceUsingStatement), Shared]
internal sealed class CSharpIntroduceUsingStatementCodeRefactoringProvider
: AbstractIntroduceUsingStatementCodeRefactoringProvider<StatementSyntax, LocalDeclarationStatementSyntax, TryStatementSyntax>
[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 CSharpIntroduceUsingStatementCodeRefactoringProvider()
: AbstractIntroduceUsingStatementCodeRefactoringProvider<
StatementSyntax,
ExpressionStatementSyntax,
LocalDeclarationStatementSyntax,
TryStatementSyntax>
{
[ImportingConstructor]
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
public CSharpIntroduceUsingStatementCodeRefactoringProvider()
{
}

protected override string CodeActionTitle => CSharpFeaturesResources.Introduce_using_statement;

protected override bool PreferSimpleUsingStatement(AnalyzerOptionsProvider options)
=> ((CSharpAnalyzerOptionsProvider)options).PreferSimpleUsingStatement.Value;

protected override bool HasCatchBlocks(TryStatementSyntax tryStatement)
=> tryStatement.Catches.Count > 0;

Expand All @@ -38,12 +42,12 @@ protected override (SyntaxList<StatementSyntax> tryStatements, SyntaxList<Statem
protected override bool CanRefactorToContainBlockStatements(SyntaxNode parent)
=> parent is BlockSyntax || parent is SwitchSectionSyntax || parent.IsEmbeddedStatementOwner();

protected override SyntaxList<StatementSyntax> GetSurroundingStatements(LocalDeclarationStatementSyntax declarationStatement)
=> declarationStatement.GetRequiredParent() switch
protected override SyntaxList<StatementSyntax> GetSurroundingStatements(StatementSyntax statement)
=> statement.GetRequiredParent() switch
{
BlockSyntax block => block.Statements,
SwitchSectionSyntax switchSection => switchSection.Statements,
_ => [declarationStatement],
_ => [statement],
};

protected override SyntaxNode WithStatements(SyntaxNode parentOfStatementsToSurround, SyntaxList<StatementSyntax> statements)
Expand All @@ -54,6 +58,28 @@ protected override SyntaxNode WithStatements(SyntaxNode parentOfStatementsToSurr
throw ExceptionUtilities.UnexpectedValue(parentOfStatementsToSurround);
}

protected override StatementSyntax CreateUsingBlockStatement(ExpressionStatementSyntax expressionStatement, SyntaxList<StatementSyntax> statementsToSurround)
=> UsingStatement(
UsingKeyword.WithLeadingTrivia(expressionStatement.GetLeadingTrivia()),
OpenParenToken,
declaration: null,
expression: expressionStatement.Expression.WithoutTrivia(),
CloseParenToken.WithTrailingTrivia(expressionStatement.GetTrailingTrivia()),
statement: Block(statementsToSurround));

protected override StatementSyntax CreateUsingLocalDeclarationStatement(
ExpressionStatementSyntax expressionStatement, SyntaxToken newVariableName)
{
return LocalDeclarationStatement(VariableDeclaration(
IdentifierName("var"),
SingletonSeparatedList(VariableDeclarator(
newVariableName,
argumentList: null,
initializer: EqualsValueClause(expressionStatement.Expression)))))
.WithUsingKeyword(UsingKeyword)
.WithSemicolonToken(expressionStatement.SemicolonToken).WithTriviaFrom(expressionStatement);
}

protected override StatementSyntax CreateUsingStatement(LocalDeclarationStatementSyntax declarationStatement, SyntaxList<StatementSyntax> statementsToSurround)
=> UsingStatement(
UsingKeyword.WithLeadingTrivia(declarationStatement.GetLeadingTrivia()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.IntroduceUsingStatement;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
Expand All @@ -16,11 +20,25 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.IntroduceUsingStatement
[Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceUsingStatement)]
public sealed class IntroduceUsingStatementTests : AbstractCSharpCodeActionTest_NoEditor
{
private static OptionsCollection DoNotPreferSimpleUsingStatement => new(LanguageNames.CSharp)
{
{ CSharpCodeStyleOptions.PreferSimpleUsingStatement, new CodeStyleOption2<bool>(false, NotificationOption2.Silent) }
};

private static OptionsCollection PreferSimpleUsingStatement => new(LanguageNames.CSharp)
{
{ CSharpCodeStyleOptions.PreferSimpleUsingStatement, new CodeStyleOption2<bool>(true, NotificationOption2.Silent) }
};

protected override CodeRefactoringProvider CreateCodeRefactoringProvider(TestWorkspace workspace, TestParameters parameters)
=> new CSharpIntroduceUsingStatementCodeRefactoringProvider();

private Task TestAsync(string initialMarkup, string expectedMarkup, LanguageVersion languageVersion = LanguageVersion.CSharp7)
=> TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(languageVersion));
private Task TestAsync(
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expectedMarkup,
LanguageVersion languageVersion = LanguageVersion.CSharp7,
OptionsCollection? options = null)
=> TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(languageVersion), options: options);

[Theory]
[InlineData("v[||]ar name = disposable;")]
Expand Down Expand Up @@ -1417,4 +1435,110 @@ void M(System.IDisposable disposable)
}
""", LanguageVersion.CSharp8);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37260")]
public async Task TestExpressionStatement()
{
await TestAsync(
"""
using System;
class C
{
void M()
{
[||]MethodThatReturnsDisposableThing();
Console.WriteLine();
}
IDisposable MethodThatReturnsDisposableThing() => null;
}
""",
"""
using System;
class C
{
void M()
{
using (MethodThatReturnsDisposableThing())
{
Console.WriteLine();
}
}
IDisposable MethodThatReturnsDisposableThing() => null;
}
""", options: DoNotPreferSimpleUsingStatement);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37260")]
public async Task TestExpressionStatement_PreferSimpleUsingStatement1()
{
await TestAsync(
"""
using System;
class C
{
void M()
{
[||]MethodThatReturnsDisposableThing();
Console.WriteLine();
}
IDisposable MethodThatReturnsDisposableThing() => null;
}
""",
"""
using System;
class C
{
void M()
{
using var _ = MethodThatReturnsDisposableThing();
Console.WriteLine();
}
IDisposable MethodThatReturnsDisposableThing() => null;
}
""", options: PreferSimpleUsingStatement);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37260")]
public async Task TestExpressionStatement_PreferSimpleUsingStatement2()
{
await TestAsync(
"""
using System;
class C
{
void M()
{
var _ = true;
[||]MethodThatReturnsDisposableThing();
Console.WriteLine();
}
IDisposable MethodThatReturnsDisposableThing() => null;
}
""",
"""
using System;
class C
{
void M()
{
var _ = true;
using var _1 = MethodThatReturnsDisposableThing();
Console.WriteLine();
}
IDisposable MethodThatReturnsDisposableThing() => null;
}
""", options: PreferSimpleUsingStatement);
}
}
Loading

0 comments on commit 38b8e68

Please sign in to comment.