diff --git a/src/Features/CSharp/Portable/IntroduceUsingStatement/CSharpIntroduceUsingStatementCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/IntroduceUsingStatement/CSharpIntroduceUsingStatementCodeRefactoringProvider.cs index 7c55cbf873ac2..9786dffe0f8e2 100644 --- a/src/Features/CSharp/Portable/IntroduceUsingStatement/CSharpIntroduceUsingStatementCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/IntroduceUsingStatement/CSharpIntroduceUsingStatementCodeRefactoringProvider.cs @@ -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; @@ -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 +[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; @@ -38,12 +42,12 @@ protected override (SyntaxList tryStatements, SyntaxList parent is BlockSyntax || parent is SwitchSectionSyntax || parent.IsEmbeddedStatementOwner(); - protected override SyntaxList GetSurroundingStatements(LocalDeclarationStatementSyntax declarationStatement) - => declarationStatement.GetRequiredParent() switch + protected override SyntaxList GetSurroundingStatements(StatementSyntax statement) + => statement.GetRequiredParent() switch { BlockSyntax block => block.Statements, SwitchSectionSyntax switchSection => switchSection.Statements, - _ => [declarationStatement], + _ => [statement], }; protected override SyntaxNode WithStatements(SyntaxNode parentOfStatementsToSurround, SyntaxList statements) @@ -54,6 +58,28 @@ protected override SyntaxNode WithStatements(SyntaxNode parentOfStatementsToSurr throw ExceptionUtilities.UnexpectedValue(parentOfStatementsToSurround); } + protected override StatementSyntax CreateUsingBlockStatement(ExpressionStatementSyntax expressionStatement, SyntaxList 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 statementsToSurround) => UsingStatement( UsingKeyword.WithLeadingTrivia(declarationStatement.GetLeadingTrivia()), diff --git a/src/Features/CSharpTest/IntroduceUsingStatement/IntroduceUsingStatementTests.cs b/src/Features/CSharpTest/IntroduceUsingStatement/IntroduceUsingStatementTests.cs index 019bf731dd3f9..880df2377d19f 100644 --- a/src/Features/CSharpTest/IntroduceUsingStatement/IntroduceUsingStatementTests.cs +++ b/src/Features/CSharpTest/IntroduceUsingStatement/IntroduceUsingStatementTests.cs @@ -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; @@ -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(false, NotificationOption2.Silent) } + }; + + private static OptionsCollection PreferSimpleUsingStatement => new(LanguageNames.CSharp) + { + { CSharpCodeStyleOptions.PreferSimpleUsingStatement, new CodeStyleOption2(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;")] @@ -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); + } } diff --git a/src/Features/Core/Portable/IntroduceUsingStatement/AbstractIntroduceUsingStatementCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceUsingStatement/AbstractIntroduceUsingStatementCodeRefactoringProvider.cs index 1d36137cf62d7..789fcb2f94aed 100644 --- a/src/Features/Core/Portable/IntroduceUsingStatement/AbstractIntroduceUsingStatementCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceUsingStatement/AbstractIntroduceUsingStatementCodeRefactoringProvider.cs @@ -11,12 +11,12 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; @@ -24,87 +24,123 @@ namespace Microsoft.CodeAnalysis.IntroduceUsingStatement; internal abstract class AbstractIntroduceUsingStatementCodeRefactoringProvider< TStatementSyntax, + TExpressionStatementSyntax, TLocalDeclarationSyntax, TTryStatementSyntax> : CodeRefactoringProvider where TStatementSyntax : SyntaxNode + where TExpressionStatementSyntax : TStatementSyntax where TLocalDeclarationSyntax : TStatementSyntax where TTryStatementSyntax : TStatementSyntax { protected abstract string CodeActionTitle { get; } + protected abstract bool PreferSimpleUsingStatement(AnalyzerOptionsProvider options); protected abstract bool CanRefactorToContainBlockStatements(SyntaxNode parent); - protected abstract SyntaxList GetSurroundingStatements(TLocalDeclarationSyntax declarationStatement); + + protected abstract SyntaxList GetSurroundingStatements(TStatementSyntax declarationStatement); protected abstract SyntaxNode WithStatements(SyntaxNode parentOfStatementsToSurround, SyntaxList statements); protected abstract bool HasCatchBlocks(TTryStatementSyntax tryStatement); protected abstract (SyntaxList tryStatements, SyntaxList finallyStatements) GetTryFinallyStatements(TTryStatementSyntax tryStatement); protected abstract TStatementSyntax CreateUsingStatement(TLocalDeclarationSyntax declarationStatement, SyntaxList statementsToSurround); + + protected abstract TStatementSyntax CreateUsingBlockStatement(TExpressionStatementSyntax expressionStatement, SyntaxList statementsToSurround); + protected abstract TStatementSyntax CreateUsingLocalDeclarationStatement(TExpressionStatementSyntax expressionStatement, SyntaxToken newVariableName); + protected abstract bool TryCreateUsingLocalDeclaration(ParseOptions options, TLocalDeclarationSyntax declarationStatement, [NotNullWhen(true)] out TLocalDeclarationSyntax? usingDeclarationStatement); public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, span, cancellationToken) = context; - var (declarationSyntax, variableName) = await FindDisposableLocalDeclarationAsync( - document, span, cancellationToken).ConfigureAwait(false); - if (declarationSyntax != null && variableName != null) - { - context.RegisterRefactoring( - CodeAction.Create( - CodeActionTitle, - cancellationToken => IntroduceUsingStatementAsync(document, declarationSyntax, variableName, cancellationToken), - CodeActionTitle), - declarationSyntax.Span); - } - } + var initialStatement = + (TStatementSyntax?)await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false) ?? + await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); - private async Task<(TLocalDeclarationSyntax? declaration, string? variableName)> FindDisposableLocalDeclarationAsync(Document document, TextSpan selection, CancellationToken cancellationToken) - { - var declarationSyntax = await document.TryGetRelevantNodeAsync(selection, cancellationToken).ConfigureAwait(false); - if (declarationSyntax is null || !CanRefactorToContainBlockStatements(declarationSyntax.GetRequiredParent())) - return default; + if (initialStatement is null) + return; - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (!CanRefactorToContainBlockStatements(initialStatement.GetRequiredParent())) + return; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var disposableType = semanticModel.Compilation.GetSpecialType(SpecialType.System_IDisposable); if (disposableType is null) - return default; + return; - var operation = semanticModel.GetOperation(declarationSyntax, cancellationToken) as IVariableDeclarationGroupOperation; - if (operation?.Declarations.Length != 1) - return default; + var syntaxFacts = document.GetRequiredLanguageService(); - var localDeclaration = operation.Declarations[0]; - if (localDeclaration.Declarators.Length != 1) - return default; + if (initialStatement is TLocalDeclarationSyntax localDeclaration) + { + if (FindDisposableLocalDeclaration(localDeclaration) is string variableName) + { + context.RegisterRefactoring( + CodeAction.Create( + CodeActionTitle, + cancellationToken => IntroduceUsingStatementAsync(document, localDeclaration, variableName, cancellationToken), + CodeActionTitle), + localDeclaration.Span); + } + } + else + { + var expressionStatement = (TExpressionStatementSyntax)initialStatement; + var expressionType = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfExpressionStatement(expressionStatement), cancellationToken).Type; + if (IsLegalUsingStatementType(semanticModel.Compilation, disposableType, expressionType)) + { + context.RegisterRefactoring( + CodeAction.Create( + CodeActionTitle, + cancellationToken => IntroduceUsingStatementAsync(document, expressionStatement, cancellationToken), + CodeActionTitle), + expressionStatement.Span); + } + } - var declarator = localDeclaration.Declarators[0]; + return; - var localType = declarator.Symbol.Type; - if (localType is null) - return default; + string? FindDisposableLocalDeclaration(TLocalDeclarationSyntax declarationSyntax) + { + var disposableType = semanticModel.Compilation.GetSpecialType(SpecialType.System_IDisposable); + if (disposableType is null) + return null; - var initializer = (localDeclaration.Initializer ?? declarator.Initializer)?.Value; + var operation = semanticModel.GetOperation(declarationSyntax, cancellationToken) as IVariableDeclarationGroupOperation; + if (operation?.Declarations.Length != 1) + return null; - // Initializer kind is invalid when incomplete declaration syntax ends in an equals token. - if (initializer is null || initializer.Kind == OperationKind.Invalid) - return default; + var localDeclaration = operation.Declarations[0]; + if (localDeclaration.Declarators.Length != 1) + return null; - if (!IsLegalUsingStatementType(semanticModel.Compilation, disposableType, localType)) - return default; + var declarator = localDeclaration.Declarators[0]; - return (declarationSyntax, declarator.Symbol.Name); + var localType = declarator.Symbol.Type; + if (localType is null) + return null; + + var initializer = (localDeclaration.Initializer ?? declarator.Initializer)?.Value; + + // Initializer kind is invalid when incomplete declaration syntax ends in an equals token. + if (initializer is null || initializer.Kind == OperationKind.Invalid) + return null; + + if (!IsLegalUsingStatementType(semanticModel.Compilation, disposableType, localType)) + return null; + + return declarator.Symbol.Name; + } } /// /// Up to date with C# 7.3. Pattern-based disposal is likely to be added to C# 8.0, /// in which case accessible instance and extension methods will need to be detected. /// - private static bool IsLegalUsingStatementType(Compilation compilation, ITypeSymbol disposableType, ITypeSymbol type) + private static bool IsLegalUsingStatementType(Compilation compilation, ITypeSymbol disposableType, [NotNullWhen(true)] ITypeSymbol? type) { // CS1674: type used in a using statement must implement 'System.IDisposable' - return compilation.ClassifyCommonConversion(type, disposableType).IsImplicit; + return type != null && compilation.ClassifyCommonConversion(type, disposableType).IsImplicit; } private async Task IntroduceUsingStatementAsync( @@ -160,32 +196,81 @@ [.. surroundingStatements { var usingStatement = CreateUsingStatement(declarationStatement, statementsToSurround); - if (statementsToSurround.Any()) - { - var newParent = WithStatements( - declarationStatement.GetRequiredParent(), - [.. surroundingStatements - .Take(declarationStatementIndex) - .Concat(usingStatement) - .Concat(surroundingStatements.Skip(declarationStatementIndex + 1 + statementsToSurround.Count))]); - - return document.WithSyntaxRoot(root.ReplaceNode( - declarationStatement.GetRequiredParent(), - newParent.WithAdditionalAnnotations(Formatter.Annotation))); - } - else - { - // Either the parent is not blocklike, meaning WithStatements can’t be used as in the other branch, - // or there’s just no need to replace more than the statement itself because no following statements - // will be surrounded. - return document.WithSyntaxRoot(root.ReplaceNode( - declarationStatement, - usingStatement.WithAdditionalAnnotations(Formatter.Annotation))); - } + return await ReplaceWithUsingStatementAsync( + document, declarationStatement, statementsToSurround, usingStatement, surroundingStatements, declarationStatementIndex, cancellationToken).ConfigureAwait(false); } } } + private async Task IntroduceUsingStatementAsync( + Document document, + TExpressionStatementSyntax expressionStatement, + CancellationToken cancellationToken) + { + var options = await document.GetAnalyzerOptionsProviderAsync(cancellationToken).ConfigureAwait(false); + + var surroundingStatements = GetSurroundingStatements(expressionStatement); + var statementIndex = surroundingStatements.IndexOf(expressionStatement); + + if (PreferSimpleUsingStatement(options)) + { + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var semanticFacts = document.GetRequiredLanguageService(); + var newName = semanticFacts.GenerateUniqueLocalName(semanticModel, expressionStatement, container: null, baseName: "_", cancellationToken); + var usingStatement = this.CreateUsingLocalDeclarationStatement(expressionStatement, newName); + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = root.ReplaceNode(expressionStatement, usingStatement); + + return document.WithSyntaxRoot(newRoot); + } + else + { + SyntaxList statementsToSurround = [.. surroundingStatements.Skip(statementIndex + 1)]; + + var usingStatement = CreateUsingBlockStatement(expressionStatement, statementsToSurround); + + return await ReplaceWithUsingStatementAsync( + document, expressionStatement, statementsToSurround, usingStatement, surroundingStatements, statementIndex, cancellationToken).ConfigureAwait(false); + } + } + + private async Task ReplaceWithUsingStatementAsync( + Document document, + TStatementSyntax statementToReplace, + SyntaxList statementsToSurround, + TStatementSyntax usingStatement, + SyntaxList surroundingStatements, + int statementIndex, + CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + if (statementsToSurround.Any()) + { + var newParent = WithStatements( + statementToReplace.GetRequiredParent(), + [.. surroundingStatements + .Take(statementIndex) + .Concat(usingStatement) + .Concat(surroundingStatements.Skip(statementIndex + 1 + statementsToSurround.Count))]); + + return document.WithSyntaxRoot(root.ReplaceNode( + statementToReplace.GetRequiredParent(), + newParent.WithAdditionalAnnotations(Formatter.Annotation))); + } + else + { + // Either the parent is not blocklike, meaning WithStatements can’t be used as in the other branch, + // or there’s just no need to replace more than the statement itself because no following statements + // will be surrounded. + return document.WithSyntaxRoot(root.ReplaceNode( + statementToReplace, + usingStatement.WithAdditionalAnnotations(Formatter.Annotation))); + } + } + private bool ShouldReplaceTryStatementWithUsing( ISyntaxFactsService syntaxFacts, string variableName, @@ -227,7 +312,7 @@ private bool ShouldReplaceTryStatementWithUsing( } private static SyntaxList GetStatementsToSurround( - TLocalDeclarationSyntax declarationStatement, + TStatementSyntax statement, SyntaxList surroundingStatements, SemanticModel semanticModel, ISyntaxFactsService syntaxFactsService, @@ -239,16 +324,16 @@ private static SyntaxList GetStatementsToSurround( // Find the minimal number of statements to move into the using block // in order to not break existing references to the local. var lastUsageStatement = FindSiblingStatementContainingLastUsage( - declarationStatement, + statement, semanticModel, syntaxFactsService, cancellationToken); - if (lastUsageStatement == declarationStatement) + if (lastUsageStatement == statement) return default; consumedLastSurroundingStatement = lastUsageStatement == surroundingStatements.Last(); - var declarationStatementIndex = surroundingStatements.IndexOf(declarationStatement); + var declarationStatementIndex = surroundingStatements.IndexOf(statement); var lastUsageStatementIndex = surroundingStatements.IndexOf(lastUsageStatement, declarationStatementIndex + 1); return [.. surroundingStatements diff --git a/src/Features/VisualBasic/Portable/IntroduceUsingStatement/VisualBasicIntroduceUsingStatementCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/IntroduceUsingStatement/VisualBasicIntroduceUsingStatementCodeRefactoringProvider.vb index 02635b9a218c6..6df686a655871 100644 --- a/src/Features/VisualBasic/Portable/IntroduceUsingStatement/VisualBasicIntroduceUsingStatementCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/IntroduceUsingStatement/VisualBasicIntroduceUsingStatementCodeRefactoringProvider.vb @@ -5,15 +5,19 @@ Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.IntroduceUsingStatement Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceUsingStatement - Friend NotInheritable Class VisualBasicIntroduceUsingStatementCodeRefactoringProvider - Inherits AbstractIntroduceUsingStatementCodeRefactoringProvider(Of StatementSyntax, LocalDeclarationStatementSyntax, TryBlockSyntax) + Inherits AbstractIntroduceUsingStatementCodeRefactoringProvider(Of + StatementSyntax, + ExpressionStatementSyntax, + LocalDeclarationStatementSyntax, + TryBlockSyntax) @@ -22,6 +26,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceUsingStatement Protected Overrides ReadOnly Property CodeActionTitle As String = VBFeaturesResources.Introduce_Using_statement + Protected Overrides Function PreferSimpleUsingStatement(options As AnalyzerOptionsProvider) As Boolean + ' VB does not have simple using statements. + Return False + End Function + Protected Overrides Function HasCatchBlocks(tryStatement As TryBlockSyntax) As Boolean Return tryStatement.CatchBlocks.Count > 0 End Function @@ -35,7 +44,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceUsingStatement Return parent.IsMultiLineExecutableBlock() End Function - Protected Overrides Function GetSurroundingStatements(declarationStatement As LocalDeclarationStatementSyntax) As SyntaxList(Of StatementSyntax) + Protected Overrides Function GetSurroundingStatements(declarationStatement As StatementSyntax) As SyntaxList(Of StatementSyntax) Return declarationStatement.GetRequiredParent().GetExecutableBlockStatements() End Function @@ -52,6 +61,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceUsingStatement Return SyntaxFactory.UsingBlock(usingStatement, statementsToSurround) End Function + Protected Overrides Function CreateUsingBlockStatement( + expressionStatement As ExpressionStatementSyntax, + statementsToSurround As SyntaxList(Of StatementSyntax)) As StatementSyntax + Dim usingStatement = SyntaxFactory.UsingStatement( + expression:=expressionStatement.Expression.WithoutTrivia(), + variables:=Nothing).WithTriviaFrom(expressionStatement) + Return SyntaxFactory.UsingBlock(usingStatement, statementsToSurround) + End Function + + Protected Overrides Function CreateUsingLocalDeclarationStatement(expressionStatement As ExpressionStatementSyntax, newVariableName As SyntaxToken) As StatementSyntax + Throw ExceptionUtilities.Unreachable() + End Function + Protected Overrides Function TryCreateUsingLocalDeclaration(options As ParseOptions, declarationStatement As LocalDeclarationStatementSyntax, ByRef usingDeclarationStatement As LocalDeclarationStatementSyntax) As Boolean Return False End Function diff --git a/src/Features/VisualBasicTest/IntroduceUsingStatement/IntroduceUsingStatementTests.vb b/src/Features/VisualBasicTest/IntroduceUsingStatement/IntroduceUsingStatementTests.vb index 2e823c8565881..b4311ec7cdea1 100644 --- a/src/Features/VisualBasicTest/IntroduceUsingStatement/IntroduceUsingStatementTests.vb +++ b/src/Features/VisualBasicTest/IntroduceUsingStatement/IntroduceUsingStatementTests.vb @@ -3,9 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports Microsoft.CodeAnalysis.CodeRefactorings -Imports Microsoft.CodeAnalysis.VisualBasic.IntroduceUsingStatement Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.VisualBasic.IntroduceUsingStatement Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.IntroduceUsingStatement @@ -746,6 +745,34 @@ Class C End Try End Using End Sub +End Class") + End Function + + + Public Async Function TestExpressionStatement() As Task + Await TestInRegularAndScriptAsync( +"Imports System + +Class C + Sub M() + [||]MethodThatReturnsDisposable() + Console.WriteLine() + End Sub + + Function MethodThatReturnsDisposable() as IDisposable + End Function +End Class", +"Imports System + +Class C + Sub M() + Using MethodThatReturnsDisposable() + Console.WriteLine() + End Using + End Sub + + Function MethodThatReturnsDisposable() as IDisposable + End Function End Class") End Function End Class