未验证 提交 cc25638a 编写于 作者: I Ivan Basov 提交者: GitHub

Merge pull request #37541 from CyrusNajmabadi/useExprLambdaSimplificaiton

Use expr/block body lambda simplification
......@@ -19,10 +19,10 @@ internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProvider
=> (new UseExpressionBodyForLambdaDiagnosticAnalyzer(), new UseExpressionBodyForLambdaCodeFixProvider());
private IDictionary<OptionKey, object> UseExpressionBody =>
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement);
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement);
private IDictionary<OptionKey, object> UseBlockBody =>
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.NeverWithSilentEnforcement);
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.NeverWithSuggestionEnforcement);
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task UseExpressionBodyInMethod()
......@@ -1165,6 +1165,126 @@ void Goo()
};
};
}
}", options: UseBlockBody);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task FixAllNested1()
{
await TestInRegularAndScriptAsync(
@"using System;
class C
{
void Goo()
{
Func<int, Func<int, string>> f = a {|FixAllInDocument:=>|}
{
return b =>
{
return b.ToString();
};
};
}
}",
@"using System;
class C
{
void Goo()
{
Func<int, Func<int, string>> f = a => b => b.ToString();
}
}", options: UseExpressionBody);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task FixAllNested2()
{
await TestInRegularAndScriptAsync(
@"using System;
class C
{
void Goo()
{
Func<int, Func<int, string>> f = a =>
{
return b {|FixAllInDocument:=>|}
{
return b.ToString();
};
};
}
}",
@"using System;
class C
{
void Goo()
{
Func<int, Func<int, string>> f = a => b => b.ToString();
}
}", options: UseExpressionBody);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task FixAllNested3()
{
await TestInRegularAndScriptAsync(
@"using System;
class C
{
void Goo()
{
Func<int, Func<int, string>> f = a {|FixAllInDocument:=>|} b => b.ToString();
}
}",
@"using System;
class C
{
void Goo()
{
Func<int, Func<int, string>> f = a =>
{
return b =>
{
return b.ToString();
};
};
}
}", options: UseBlockBody);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task FixAllNested4()
{
await TestInRegularAndScriptAsync(
@"using System;
class C
{
void Goo()
{
Func<int, Func<int, string>> f = a => b {|FixAllInDocument:=>|} b.ToString();
}
}",
@"using System;
class C
{
void Goo()
{
Func<int, Func<int, string>> f = a =>
{
return b =>
{
return b.ToString();
};
};
}
}", options: UseBlockBody);
}
}
......
......@@ -11,7 +11,7 @@
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBodyForLambdas
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody
{
public class UseExpressionBodyForLambdasRefactoringTests : AbstractCSharpCodeActionTest
{
......@@ -19,28 +19,28 @@ protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspa
=> new UseExpressionBodyForLambdaCodeRefactoringProvider();
private IDictionary<OptionKey, object> UseExpressionBody =>
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement);
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement);
private IDictionary<OptionKey, object> UseExpressionBodyDisabledDiagnostic =>
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, new CodeStyleOption<ExpressionBodyPreference>(ExpressionBodyPreference.WhenPossible, NotificationOption.None));
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement);
private IDictionary<OptionKey, object> UseBlockBody =>
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.NeverWithSilentEnforcement);
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.NeverWithSuggestionEnforcement);
private IDictionary<OptionKey, object> UseBlockBodyDisabledDiagnostic =>
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, new CodeStyleOption<ExpressionBodyPreference>(ExpressionBodyPreference.Never, NotificationOption.None));
this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, CSharpCodeStyleOptions.NeverWithSilentEnforcement);
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task TestNotOfferedIfUserPrefersExpressionBodiesAndInBlockBody()
{
await TestMissingAsync(
@"
using System;
@"using System;
class C
{
void Goo()
{
Func<int, string> f = x [||]=>
Func<int, string> f = x [|=>|]
{
return x.ToString();
}
......@@ -52,25 +52,25 @@ void Goo()
public async Task TestOfferedIfUserPrefersExpressionBodiesWithoutDiagnosticAndInBlockBody()
{
await TestInRegularAndScript1Async(
@"
using System;
@"using System;
class C
{
void Goo()
{
Func<int, string> f = x [||]=> x.ToString();
Func<int, string> f = x [||]=>
{
return x.ToString();
};
}
}",
@"
using System;
@"using System;
class C
{
void Goo()
{
Func<int, string> f = x =>
{
return x.ToString();
};
Func<int, string> f = x => x.ToString();
}
}", parameters: new TestParameters(options: UseExpressionBodyDisabledDiagnostic));
}
......@@ -79,8 +79,8 @@ void Goo()
public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody()
{
await TestInRegularAndScript1Async(
@"
using System;
@"using System;
class C
{
void Goo()
......@@ -91,8 +91,8 @@ void Goo()
};
}
}",
@"
using System;
@"using System;
class C
{
void Goo()
......@@ -102,12 +102,25 @@ void Goo()
}", parameters: new TestParameters(options: UseBlockBody));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task TestNotOfferedInMethod()
{
await TestMissingAsync(
@"class C
{
int [|Goo|]()
{
return 1;
}
}", parameters: new TestParameters(options: UseBlockBody));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody()
{
await TestMissingAsync(
@"
using System;
@"using System;
class C
{
void Goo()
......@@ -121,8 +134,8 @@ void Goo()
public async Task TestOfferedIfUserPrefersBlockBodiesWithoutDiagnosticAndInExpressionBody()
{
await TestInRegularAndScript1Async(
@"
using System;
@"using System;
class C
{
void Goo()
......@@ -130,8 +143,8 @@ void Goo()
Func<int, string> f = x [||]=> x.ToString();
}
}",
@"
using System;
@"using System;
class C
{
void Goo()
......@@ -148,8 +161,8 @@ void Goo()
public async Task TestOfferedIfUserPrefersExpressionBodiesAndInExpressionBody()
{
await TestInRegularAndScript1Async(
@"
using System;
@"using System;
class C
{
void Goo()
......@@ -157,8 +170,8 @@ void Goo()
Func<int, string> f = x [||]=> x.ToString();
}
}",
@"
using System;
@"using System;
class C
{
void Goo()
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda
{
using static UseExpressionBodyForLambdaHelpers;
[ExportCodeRefactoringProvider(LanguageNames.CSharp,
Name = PredefinedCodeRefactoringProviderNames.UseExpressionBody), Shared]
internal sealed class UseExpressionBodyForLambdaCodeRefactoringProvider : CodeRefactoringProvider
{
[ImportingConstructor]
public UseExpressionBodyForLambdaCodeRefactoringProvider()
{
}
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var (document, textSpan, cancellationToken) = context;
if (textSpan.Length > 0)
{
return;
}
var position = textSpan.Start;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var lambdaNode = root.FindToken(position).Parent.FirstAncestorOrSelf<LambdaExpressionSyntax>();
if (lambdaNode == null)
{
return;
}
var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
if (CanOfferUseExpressionBody(optionSet, lambdaNode, forAnalyzer: false))
{
context.RegisterRefactoring(new MyCodeAction(
UseExpressionBodyTitle.ToString(),
c => UpdateDocumentAsync(
document, root, lambdaNode,
useExpressionBody: true, c)));
}
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var (canOffer, _) = CanOfferUseBlockBody(
semanticModel, optionSet, lambdaNode, forAnalyzer: false, cancellationToken);
if (canOffer)
{
context.RegisterRefactoring(new MyCodeAction(
UseBlockBodyTitle.ToString(),
c => UpdateDocumentAsync(
document, root, lambdaNode,
useExpressionBody: false, c)));
}
}
private async Task<Document> UpdateDocumentAsync(
Document document, SyntaxNode root, LambdaExpressionSyntax declaration,
bool useExpressionBody, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
// We're only replacing a single declaration in the refactoring. So pass 'declaration'
// as both the 'original' and 'current' declaration.
var updatedDeclaration = Update(semanticModel, useExpressionBody, declaration, declaration);
var newRoot = root.ReplaceNode(declaration, updatedDeclaration);
return document.WithSyntaxRoot(newRoot);
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
......@@ -12,82 +16,81 @@
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda
{
/// <summary>
/// Helper class that allows us to share lots of logic between the diagnostic analyzer and the
/// code refactoring provider. Those can't share a common base class due to their own inheritance
/// requirements with <see cref="DiagnosticAnalyzer"/> and <see cref="CodeRefactoringProvider"/>.
/// </summary>
internal static class UseExpressionBodyForLambdaHelpers
internal partial class UseExpressionBodyForLambdaCodeStyleProvider
: AbstractCodeStyleProvider<ExpressionBodyPreference, UseExpressionBodyForLambdaCodeStyleProvider>
{
public static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_lambda_expressions), FeaturesResources.ResourceManager, typeof(FeaturesResources));
public static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_lambda_expressions), FeaturesResources.ResourceManager, typeof(FeaturesResources));
private static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_lambda_expressions), FeaturesResources.ResourceManager, typeof(FeaturesResources));
private static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_lambda_expressions), FeaturesResources.ResourceManager, typeof(FeaturesResources));
public UseExpressionBodyForLambdaCodeStyleProvider()
: base(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas,
LanguageNames.CSharp,
IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId,
UseExpressionBodyTitle,
UseExpressionBodyTitle)
{
}
public static ExpressionSyntax GetExpressionBody(LambdaExpressionSyntax declaration)
// Shared code needed by all parts of the style provider for this feature.
private static ExpressionSyntax GetBodyAsExpression(LambdaExpressionSyntax declaration)
=> declaration.Body as ExpressionSyntax;
public static bool CanOfferUseExpressionBody(
OptionSet optionSet, LambdaExpressionSyntax declaration, bool forAnalyzer)
private static bool CanOfferUseExpressionBody(
ExpressionBodyPreference preference, LambdaExpressionSyntax declaration)
{
var currentOptionValue = optionSet.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas);
var preference = currentOptionValue.Value;
var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never;
var analyzerDisabled = currentOptionValue.Notification.Severity == ReportDiagnostic.Suppress;
// If the user likes expression bodies, then we offer expression bodies from the diagnostic analyzer.
// If the user does not like expression bodies then we offer expression bodies from the refactoring provider.
// If the analyzer is disabled completely, the refactoring is enabled in both directions.
if (userPrefersExpressionBodies == forAnalyzer || (!forAnalyzer && analyzerDisabled))
if (!userPrefersExpressionBodies)
{
var expressionBody = GetExpressionBody(declaration);
if (expressionBody == null)
{
// They don't have an expression body. See if we could convert the block they
// have into one.
var options = declaration.SyntaxTree.Options;
var conversionPreference = forAnalyzer ? preference : ExpressionBodyPreference.WhenPossible;
// If the user doesn't even want expression bodies, then certainly do not offer.
return false;
}
return TryConvertToExpressionBody(declaration, options, conversionPreference, out _, out _);
}
var expressionBody = GetBodyAsExpression(declaration);
if (expressionBody != null)
{
// they already have an expression body. so nothing to do here.
return false;
}
return false;
// They don't have an expression body. See if we could convert the block they
// have into one.
var options = declaration.SyntaxTree.Options;
return TryConvertToExpressionBody(declaration, options, preference, out _, out _);
}
private static bool TryConvertToExpressionBody(
LambdaExpressionSyntax declaration,
ParseOptions options, ExpressionBodyPreference conversionPreference,
out ExpressionSyntax expressionWhenOnSingleLine,
out SyntaxToken semicolonWhenOnSingleLine)
{
return TryConvertToExpressionBodyWorker(
declaration, options, conversionPreference,
out expressionWhenOnSingleLine, out semicolonWhenOnSingleLine);
}
private static bool TryConvertToExpressionBodyWorker(
LambdaExpressionSyntax declaration, ParseOptions options, ExpressionBodyPreference conversionPreference,
out ExpressionSyntax expressionWhenOnSingleLine, out SyntaxToken semicolonWhenOnSingleLine)
out ExpressionSyntax expression, out SyntaxToken semicolon)
{
var body = declaration.Body as BlockSyntax;
return body.TryConvertToExpressionBody(
declaration.Kind(), options, conversionPreference,
out expressionWhenOnSingleLine, out semicolonWhenOnSingleLine);
out expression, out semicolon);
}
public static (bool canOffer, bool fixesError) CanOfferUseBlockBody(
SemanticModel semanticModel, OptionSet optionSet,
LambdaExpressionSyntax declaration, bool forAnalyzer,
CancellationToken cancellationToken)
private static bool CanOfferUseBlockBody(
SemanticModel semanticModel, ExpressionBodyPreference preference,
LambdaExpressionSyntax declaration, CancellationToken cancellationToken)
{
var expressionBodyOpt = GetExpressionBody(declaration);
var userPrefersBlockBodies = preference == ExpressionBodyPreference.Never;
if (!userPrefersBlockBodies)
{
// If the user doesn't even want block bodies, then certainly do not offer.
return false;
}
var expressionBodyOpt = GetBodyAsExpression(declaration);
if (expressionBodyOpt == null)
{
return (canOffer: false, fixesError: false);
// they already have a block body.
return false;
}
// We need to know what sort of lambda this is (void returning or not) in order to be
......@@ -97,44 +100,38 @@ public static ExpressionSyntax GetExpressionBody(LambdaExpressionSyntax declarat
var lambdaType = semanticModel.GetTypeInfo(declaration, cancellationToken).ConvertedType as INamedTypeSymbol;
if (lambdaType == null || lambdaType.DelegateInvokeMethod == null)
{
return (canOffer: false, fixesError: false);
return false;
}
var canOffer = expressionBodyOpt.TryConvertToStatement(
semicolonTokenOpt: default, createReturnStatementForExpression: false, out var statement) == true;
semicolonTokenOpt: default, createReturnStatementForExpression: false, out _);
if (!canOffer)
{
return (canOffer: false, fixesError: false);
// Couldn't even convert the expression into statement form.
return false;
}
var languageVersion = ((CSharpParseOptions)declaration.SyntaxTree.Options).LanguageVersion;
if (expressionBodyOpt.IsKind(SyntaxKind.ThrowExpression) &&
languageVersion < LanguageVersion.CSharp7)
{
// If they're using a throw expression in a declaration and it's prior to C# 7
// then always mark this as something that can be fixed by the analyzer. This way
// we'll also get 'fix all' working to fix all these cases.
return (canOffer, fixesError: true);
// Can't convert this prior to C# 7 because ```a => throw ...``` isn't allowed.
return false;
}
var currentOptionValue = optionSet.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas);
var preference = currentOptionValue.Value;
var userPrefersBlockBodies = preference == ExpressionBodyPreference.Never;
var analyzerDisabled = currentOptionValue.Notification.Severity == ReportDiagnostic.Suppress;
// If the user likes block bodies, then we offer block bodies from the diagnostic analyzer.
// If the user does not like block bodies then we offer block bodies from the refactoring provider.
// If the analyzer is disabled completely, the refactoring is enabled in both directions.
canOffer = userPrefersBlockBodies == forAnalyzer || (!forAnalyzer && analyzerDisabled);
return (canOffer, fixesError: false);
return true;
}
public static LambdaExpressionSyntax Update(
SemanticModel semanticModel, bool useExpressionBody,
LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration)
private static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration)
=> UpdateWorker(semanticModel, originalDeclaration, currentDeclaration).WithAdditionalAnnotations(Formatter.Annotation);
private static LambdaExpressionSyntax UpdateWorker(
SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration)
{
return UpdateWorker(semanticModel, useExpressionBody, originalDeclaration, currentDeclaration)
.WithAdditionalAnnotations(Formatter.Annotation);
var expressionBody = GetBodyAsExpression(currentDeclaration);
return expressionBody == null
? WithExpressionBody(currentDeclaration)
: WithBlockBody(semanticModel, originalDeclaration, currentDeclaration);
}
private static LambdaExpressionSyntax UpdateWorker(
......@@ -171,15 +168,15 @@ private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax
private static LambdaExpressionSyntax WithBlockBody(
SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration)
{
var expressionBody = GetExpressionBody(currentDeclaration);
var createReturnStatementForExpression = CreateReturnStatementForExpression(semanticModel, originalDeclaration);
var expressionBody = GetBodyAsExpression(currentDeclaration);
var createReturnStatementForExpression = CreateReturnStatementForExpression(
semanticModel, originalDeclaration);
if (!expressionBody.TryConvertToStatement(
semicolonTokenOpt: default,
createReturnStatementForExpression,
out var statement))
{
return currentDeclaration;
}
......@@ -225,5 +222,30 @@ private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax
return true;
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
// Stub classes needed only for exporting purposes.
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseExpressionBodyForLambdaCodeFixProvider)), Shared]
internal sealed class UseExpressionBodyForLambdaCodeFixProvider : UseExpressionBodyForLambdaCodeStyleProvider.CodeFixProvider
{
}
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(UseExpressionBodyForLambdaCodeRefactoringProvider)), Shared]
internal sealed class UseExpressionBodyForLambdaCodeRefactoringProvider : UseExpressionBodyForLambdaCodeStyleProvider.CodeRefactoringProvider
{
}
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : UseExpressionBodyForLambdaCodeStyleProvider.DiagnosticAnalyzer
{
}
}
......@@ -3,52 +3,27 @@
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda
{
using static UseExpressionBodyForLambdaHelpers;
// Code for the DiagnosticAnalyzer ("Analysis") portion of the feature.
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
internal partial class UseExpressionBodyForLambdaCodeStyleProvider
{
public const string FixesError = nameof(FixesError);
protected override void DiagnosticAnalyzerInitialize(AnalysisContext context)
=> context.RegisterSyntaxNodeAction(AnalyzeSyntax,
SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression);
public UseExpressionBodyForLambdaDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId,
CSharpCodeStyleOptions.PreferExpressionBodiedLambdas,
LanguageNames.CSharp,
UseExpressionBodyTitle)
{
}
public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
protected override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
protected override void InitializeWorker(AnalysisContext context)
=> context.RegisterSyntaxNodeAction(
AnalyzeSyntax,
SyntaxKind.SimpleLambdaExpression,
SyntaxKind.ParenthesizedLambdaExpression);
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOption<ExpressionBodyPreference> option)
{
var options = context.Options;
var syntaxTree = context.Node.SyntaxTree;
var cancellationToken = context.CancellationToken;
var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();
if (optionSet == null)
{
return;
}
var declaration = (LambdaExpressionSyntax)context.Node;
var diagnostic = AnalyzeSyntax(
context.SemanticModel, optionSet, declaration, cancellationToken);
var diagnostic = AnalyzeSyntax(context.SemanticModel, option, declaration, context.CancellationToken);
if (diagnostic != null)
{
context.ReportDiagnostic(diagnostic);
......@@ -56,41 +31,31 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
}
private Diagnostic AnalyzeSyntax(
SemanticModel semanticModel, OptionSet optionSet,
SemanticModel semanticModel, CodeStyleOption<ExpressionBodyPreference> option,
LambdaExpressionSyntax declaration, CancellationToken cancellationToken)
{
var preferExpressionBodiedOption = optionSet.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas);
var severity = preferExpressionBodiedOption.Notification.Severity;
if (CanOfferUseExpressionBody(optionSet, declaration, forAnalyzer: true))
if (CanOfferUseExpressionBody(option.Value, declaration))
{
var location = GetDiagnosticLocation(declaration);
var additionalLocations = ImmutableArray.Create(declaration.GetLocation());
var properties = ImmutableDictionary<string, string>.Empty.Add(nameof(UseExpressionBody), "");
var properties = ImmutableDictionary<string, string>.Empty;
return DiagnosticHelper.Create(
CreateDescriptorWithId(DescriptorId, UseExpressionBodyTitle, UseExpressionBodyTitle),
location, severity, additionalLocations, properties);
CreateDescriptorWithId(UseExpressionBodyTitle, UseExpressionBodyTitle),
location, option.Notification.Severity, additionalLocations, properties);
}
var (canOffer, fixesError) = CanOfferUseBlockBody(
semanticModel, optionSet, declaration, forAnalyzer: true, cancellationToken);
if (canOffer)
if (CanOfferUseBlockBody(semanticModel, option.Value, declaration, cancellationToken))
{
// They have an expression body. Create a diagnostic to convert it to a block
// if they don't want expression bodies for this member.
var location = GetDiagnosticLocation(declaration);
var properties = ImmutableDictionary<string, string>.Empty;
if (fixesError)
{
properties = properties.Add(FixesError, "");
}
var additionalLocations = ImmutableArray.Create(declaration.GetLocation());
return DiagnosticHelper.Create(
CreateDescriptorWithId(DescriptorId, UseBlockBodyTitle, UseBlockBodyTitle),
location, severity, additionalLocations, properties);
CreateDescriptorWithId(UseBlockBodyTitle, UseBlockBodyTitle),
location, option.Notification.Severity, additionalLocations, properties);
}
return null;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda
{
using static UseExpressionBodyForLambdaHelpers;
// Code for the CodeFixProvider ("Fixing") portion of the feature.
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
internal sealed partial class UseExpressionBodyForLambdaCodeFixProvider : SyntaxEditorBasedCodeFixProvider
internal partial class UseExpressionBodyForLambdaCodeStyleProvider
{
[ImportingConstructor]
public UseExpressionBodyForLambdaCodeFixProvider()
protected override Task<ImmutableArray<CodeAction>> ComputeCodeActionsAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
{
}
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId);
internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle;
protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic)
=> !diagnostic.IsSuppressed ||
diagnostic.Properties.ContainsKey(UseExpressionBodyForLambdaDiagnosticAnalyzer.FixesError);
public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics.First();
var priority = diagnostic.Severity == DiagnosticSeverity.Hidden
? CodeActionPriority.Low
: CodeActionPriority.Medium;
var codeAction = new MyCodeAction(
diagnostic.GetMessage(),
c => FixWithSyntaxEditorAsync(document, diagnostic, c));
context.RegisterCodeFix(
new MyCodeAction(diagnostic.GetMessage(), priority, c => FixAsync(context.Document, diagnostic, c)),
diagnostic);
return Task.CompletedTask;
return Task.FromResult(ImmutableArray.Create<CodeAction>(codeAction));
}
protected override async Task FixAllAsync(
......@@ -68,22 +41,10 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var declarationLocation = diagnostic.AdditionalLocations[0];
var originalDeclaration = (LambdaExpressionSyntax)declarationLocation.FindNode(getInnermostNodeForTie: true, cancellationToken);
var useExpressionBody = diagnostic.Properties.ContainsKey(nameof(UseExpressionBody));
editor.ReplaceNode(
originalDeclaration,
(currentDeclaration, g) => Update(semanticModel, useExpressionBody, originalDeclaration, (LambdaExpressionSyntax)currentDeclaration));
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
internal override CodeActionPriority Priority { get; }
public MyCodeAction(string title, CodeActionPriority priority, Func<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
this.Priority = priority;
}
(current, _) => Update(semanticModel, originalDeclaration, (LambdaExpressionSyntax)current));
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda
{
// Code for the CodeRefactoringProvider ("Refactoring") portion of the feature.
internal partial class UseExpressionBodyForLambdaCodeStyleProvider
{
protected override async Task<ImmutableArray<CodeAction>> ComputeOpposingRefactoringsWhenAnalyzerActiveAsync(
Document document, TextSpan span, ExpressionBodyPreference option, CancellationToken cancellationToken)
{
if (option == ExpressionBodyPreference.Never)
{
// the user wants block-bodies (and the analyzer will be trying to enforce that). So
// the reverse of this is that we want to offer the refactoring to convert a
// block-body to an expression-body whenever possible.
return await ComputeRefactoringsAsync(
document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false);
}
else if (option == ExpressionBodyPreference.WhenPossible)
{
// the user likes expression-bodies whenever possible, and the analyzer will be
// trying to enforce that. So the reverse of this is that we want to offer the
// refactoring to convert an expression-body to a block-body whenever possible.
return await ComputeRefactoringsAsync(
document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false);
}
else if (option == ExpressionBodyPreference.WhenOnSingleLine)
{
// the user likes expression-bodies *if* the body would be on a single line. this
// means if we hit an block-body with an expression on a single line, then the
// analyzer will handle it for us.
// So we need to handle the cases of either hitting an expression-body and wanting
// to convert it to a block-body *or* hitting an block-body over *multiple* lines and
// wanting to offer to convert to an expression-body.
// Always offer to convert an expression to a block since the analyzer will never
// offer that. For this option setting.
var useBlockRefactorings = await ComputeRefactoringsAsync(
document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false);
var whenOnSingleLineRefactorings = await ComputeRefactoringsAsync(
document, span, ExpressionBodyPreference.WhenOnSingleLine, cancellationToken).ConfigureAwait(false);
if (whenOnSingleLineRefactorings.Length > 0)
{
// this block lambda would be converted to an expression lambda based on the
// analyzer alone. So we don't want to offer that as a refactoring ourselves.
return useBlockRefactorings;
}
// The lambda block statement wasn't on a single line. So the analyzer would
// not offer to convert it to an expression body. So we should can offer that
// as a refactoring if possible.
var whenPossibleRefactorings = await ComputeRefactoringsAsync(
document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false);
return useBlockRefactorings.AddRange(whenPossibleRefactorings);
}
else
{
throw ExceptionUtilities.UnexpectedValue(option);
}
}
protected override async Task<ImmutableArray<CodeAction>> ComputeAllRefactoringsWhenAnalyzerInactiveAsync(
Document document, TextSpan span, CancellationToken cancellationToken)
{
// If the analyzer is inactive, then we want to offer refactorings in any viable
// direction. So we want to offer to convert expression-bodies to block-bodies, and
// vice-versa if applicable.
var toExpressionBodyRefactorings = await ComputeRefactoringsAsync(
document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false);
var toBlockBodyRefactorings = await ComputeRefactoringsAsync(
document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false);
return toExpressionBodyRefactorings.AddRange(toBlockBodyRefactorings);
}
private async Task<ImmutableArray<CodeAction>> ComputeRefactoringsAsync(
Document document, TextSpan span, ExpressionBodyPreference option, CancellationToken cancellationToken)
{
if (span.Length > 0)
{
return ImmutableArray<CodeAction>.Empty;
}
var position = span.Start;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var lambdaNode = root.FindToken(position).Parent.FirstAncestorOrSelf<LambdaExpressionSyntax>();
if (lambdaNode == null)
{
return ImmutableArray<CodeAction>.Empty;
}
var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var result = ArrayBuilder<CodeAction>.GetInstance();
if (CanOfferUseExpressionBody(option, lambdaNode))
{
result.Add(new MyCodeAction(
UseExpressionBodyTitle.ToString(),
c => UpdateDocumentAsync(
document, root, lambdaNode, c)));
}
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
if (CanOfferUseBlockBody(semanticModel, option, lambdaNode, cancellationToken))
{
result.Add(new MyCodeAction(
UseBlockBodyTitle.ToString(),
c => UpdateDocumentAsync(
document, root, lambdaNode, c)));
}
return result.ToImmutableAndFree();
}
private async Task<Document> UpdateDocumentAsync(
Document document, SyntaxNode root, LambdaExpressionSyntax declaration, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
// We're only replacing a single declaration in the refactoring. So pass 'declaration'
// as both the 'original' and 'current' declaration.
var updatedDeclaration = Update(semanticModel, declaration, declaration);
var newRoot = root.ReplaceNode(declaration, updatedDeclaration);
return document.WithSyntaxRoot(newRoot);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Options;
namespace Microsoft.CodeAnalysis.CodeStyle
{
// This part contains all the logic for hooking up the DiagnosticAnalyzer to the CodeStyleProvider.
// All the code in this part is an implementation detail and is intentionally private so that
// subclasses cannot change anything. All code relevant to subclasses relating to analysis
// is contained in AbstractCodeStyleProvider.cs
internal abstract partial class AbstractCodeStyleProvider<TOptionKind, TCodeStyleProvider>
{
public abstract class DiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
public readonly TCodeStyleProvider _codeStyleProvider;
protected DiagnosticAnalyzer(bool configurable = true)
: this(new TCodeStyleProvider(), configurable)
{
}
private DiagnosticAnalyzer(TCodeStyleProvider codeStyleProvider, bool configurable)
: base(codeStyleProvider._descriptorId,
codeStyleProvider._option,
codeStyleProvider._language,
codeStyleProvider._title,
codeStyleProvider._message,
configurable)
{
_codeStyleProvider = codeStyleProvider;
}
protected sealed override void InitializeWorker(Diagnostics.AnalysisContext context)
=> _codeStyleProvider.DiagnosticAnalyzerInitialize(new AnalysisContext(_codeStyleProvider, context));
public sealed override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> _codeStyleProvider.GetAnalyzerCategory();
}
/// <summary>
/// Critically, we want to consolidate the logic about checking if the analyzer should run
/// at all. i.e. if the user has their option set to 'none' or 'refactoring only' then we
/// do not want the analyzer to run at all.
///
/// To that end, we don't let the subclass have direct access to the real <see
/// cref="Diagnostics.AnalysisContext"/>. Instead, we pass this type to the subclass for it
/// register with. We then check if the registration should proceed given the <see
/// cref="CodeStyleOption{T}"/>
/// and the current <see cref="SyntaxTree"/> being processed. If not, we don't do the
/// actual registration.
/// </summary>
protected struct AnalysisContext
{
private readonly TCodeStyleProvider _codeStyleProvider;
private readonly Diagnostics.AnalysisContext _context;
public AnalysisContext(TCodeStyleProvider codeStyleProvider, Diagnostics.AnalysisContext context)
{
_codeStyleProvider = codeStyleProvider;
_context = context;
}
public void RegisterCompilationStartAction(Action<Compilation, AnalysisContext> analyze)
{
var _this = this;
_context.RegisterCompilationStartAction(
c => analyze(c.Compilation, _this));
}
public void RegisterCodeBlockAction(Action<CodeBlockAnalysisContext, CodeStyleOption<TOptionKind>> analyze)
{
var provider = _codeStyleProvider;
_context.RegisterCodeBlockAction(
c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.SemanticModel.SyntaxTree, c.CancellationToken));
}
public void RegisterSemanticModelAction(Action<SemanticModelAnalysisContext, CodeStyleOption<TOptionKind>> analyze)
{
var provider = _codeStyleProvider;
_context.RegisterSemanticModelAction(
c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.SemanticModel.SyntaxTree, c.CancellationToken));
}
public void RegisterSyntaxTreeAction(Action<SyntaxTreeAnalysisContext, CodeStyleOption<TOptionKind>> analyze)
{
var provider = _codeStyleProvider;
_context.RegisterSyntaxTreeAction(
c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.Tree, c.CancellationToken));
}
public void RegisterOperationAction(
Action<OperationAnalysisContext, CodeStyleOption<TOptionKind>> analyze,
params OperationKind[] operationKinds)
{
var provider = _codeStyleProvider;
_context.RegisterOperationAction(
c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.Operation.SemanticModel.SyntaxTree, c.CancellationToken),
operationKinds);
}
public void RegisterSyntaxNodeAction<TSyntaxKind>(
Action<SyntaxNodeAnalysisContext, CodeStyleOption<TOptionKind>> analyze,
params TSyntaxKind[] syntaxKinds) where TSyntaxKind : struct
{
var provider = _codeStyleProvider;
_context.RegisterSyntaxNodeAction(
c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.SemanticModel.SyntaxTree, c.CancellationToken),
syntaxKinds);
}
private static void AnalyzeIfEnabled<TContext>(
TCodeStyleProvider provider, TContext context, Action<TContext, CodeStyleOption<TOptionKind>> analyze,
AnalyzerOptions options, SyntaxTree syntaxTree, CancellationToken cancellationToken)
{
var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();
if (optionSet == null)
{
return;
}
var optionValue = optionSet.GetOption(provider._option);
var severity = GetOptionSeverity(optionValue);
switch (severity)
{
case ReportDiagnostic.Error:
case ReportDiagnostic.Warn:
case ReportDiagnostic.Info:
break;
default:
// don't analyze if it's any other value.
return;
}
analyze(context, optionValue);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
namespace Microsoft.CodeAnalysis.CodeStyle
{
// This part contains all the logic for hooking up the CodeFixProvider to the CodeStyleProvider.
// All the code in this part is an implementation detail and is intentionally private so that
// subclasses cannot change anything. All code relevant to subclasses relating to fixing is
// contained in AbstractCodeStyleProvider.cs
internal abstract partial class AbstractCodeStyleProvider<TOptionKind, TCodeStyleProvider>
{
private async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var document = context.Document;
var diagnostic = context.Diagnostics[0];
var cancellationToken = context.CancellationToken;
var codeFixes = await ComputeCodeActionsAsync(
document, diagnostic, cancellationToken).ConfigureAwait(false);
context.RegisterFixes(codeFixes, context.Diagnostics);
}
public abstract class CodeFixProvider : SyntaxEditorBasedCodeFixProvider
{
public readonly TCodeStyleProvider _codeStyleProvider;
protected CodeFixProvider()
{
_codeStyleProvider = new TCodeStyleProvider();
FixableDiagnosticIds = ImmutableArray.Create(_codeStyleProvider._descriptorId);
}
internal override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle;
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; }
public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
=> _codeStyleProvider.RegisterCodeFixesAsync(context);
protected sealed override Task FixAllAsync(Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken)
=> _codeStyleProvider.FixAllAsync(document, diagnostics, editor, cancellationToken);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings;
namespace Microsoft.CodeAnalysis.CodeStyle
{
// This part contains all the logic for hooking up the CodeRefactoring to the CodeStyleProvider.
// All the code in this part is an implementation detail and is intentionally private so that
// subclasses cannot change anything. All code relevant to subclasses relating to refactorings
// is contained in AbstractCodeStyleProvider.cs
internal abstract partial class AbstractCodeStyleProvider<TOptionKind, TCodeStyleProvider>
{
private async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var (document, _, cancellationToken) = context;
var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var optionValue = optionSet.GetOption(_option);
var severity = GetOptionSeverity(optionValue);
switch (severity)
{
case ReportDiagnostic.Suppress:
case ReportDiagnostic.Hidden:
// if the severity is Hidden that's equivalent to 'refactoring only', so we want
// to try to compute the refactoring here.
//
// If the severity is 'suppress', that means the user doesn't want the actual
// analyzer to run here. However, we can still check to see if we could offer
// the feature here as a refactoring.
await ComputeRefactoringsAsync(context, optionValue.Value, analyzerActive: false).ConfigureAwait(false);
return;
case ReportDiagnostic.Error:
case ReportDiagnostic.Warn:
case ReportDiagnostic.Info:
// User has this option set at a level where we want it checked by the
// DiagnosticAnalyser and not the CodeRefactoringProvider. However, we still
// want to check if we want to offer the *reverse* refactoring here in this
// single location.
//
// For example, say this is the "use expression body" feature. If the user says
// they always prefer expression-bodies (with warning level), then we want the
// analyzer to always be checking for that. However, we still want to offer the
// refactoring to flip their code to use a block body here, just in case that
// was something they wanted to do as a one off (i.e. before adding new
// statements.
//
// TODO(cyrusn): Should we only do this for warn/info? Argument could be made
// that we shouldn't even offer to refactor in the reverse direction if it will
// just cause an error. That said, maybe this is just an intermediary step, and
// we shouldn't really be blocking the user from making it.
await ComputeRefactoringsAsync(context, optionValue.Value, analyzerActive: true).ConfigureAwait(false);
return;
}
}
private async Task ComputeRefactoringsAsync(
CodeRefactoringContext context, TOptionKind option, bool analyzerActive)
{
var (document, span, cancellationToken) = context;
var computationTask = analyzerActive
? ComputeOpposingRefactoringsWhenAnalyzerActiveAsync(document, span, option, cancellationToken)
: ComputeAllRefactoringsWhenAnalyzerInactiveAsync(document, span, cancellationToken);
var codeActions = await computationTask.ConfigureAwait(false);
context.RegisterRefactorings(codeActions);
}
public class CodeRefactoringProvider : CodeRefactorings.CodeRefactoringProvider
{
public readonly TCodeStyleProvider _codeStyleProvider;
protected CodeRefactoringProvider()
{
_codeStyleProvider = new TCodeStyleProvider();
}
public sealed override Task ComputeRefactoringsAsync(CodeRefactoringContext context)
=> _codeStyleProvider.ComputeRefactoringsAsync(context);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CodeStyle
{
// This file contains the "protected" surface area of the AbstractCodeStyleProvider.
// It specifically is all the extensibility surface that a subclass needs to fill in
// in order to properly expose a code style analyzer/fixer/refactoring.
/// <summary>
/// This is the core class a code-style feature needs to derive from. All logic related to the
/// feature will then be contained in this class. This class will take care of many bit of
/// common logic that all code style providers would have to care about and can thus do that
/// logic in a consistent fashion without all providers having to do the same. For example,
/// this class will check the current value of the code style option. If it is 'refactoring
/// only', it will not bother running any of the DiagnosticAnalyzer codepaths, and will only run
/// the CodeRefactoringProvider codepaths.
/// </summary>
internal abstract partial class AbstractCodeStyleProvider<
TOptionKind, TCodeStyleProvider>
where TCodeStyleProvider : AbstractCodeStyleProvider<TOptionKind, TCodeStyleProvider>, new()
{
private readonly Option<CodeStyleOption<TOptionKind>> _option;
private readonly string _language;
private readonly string _descriptorId;
private readonly LocalizableString _title;
private readonly LocalizableString _message;
protected AbstractCodeStyleProvider(
Option<CodeStyleOption<TOptionKind>> option,
string language,
string descriptorId,
LocalizableString title,
LocalizableString message)
{
_option = option;
_language = language;
_descriptorId = descriptorId;
_title = title;
_message = message;
}
/// <summary>
/// Helper to get the true ReportDiagnostic severity for a given option. Importantly, this
/// handle ReportDiagnostic.Default and will map that back to the appropriate value in that
/// case.
/// </summary>
protected static ReportDiagnostic GetOptionSeverity(CodeStyleOption<TOptionKind> optionValue)
{
var severity = optionValue.Notification.Severity;
return severity == ReportDiagnostic.Default
? severity.WithDefaultSeverity(DiagnosticSeverity.Hidden)
: severity;
}
#region analysis
protected abstract void DiagnosticAnalyzerInitialize(AnalysisContext context);
protected abstract DiagnosticAnalyzerCategory GetAnalyzerCategory();
protected DiagnosticDescriptor CreateDescriptorWithId(
LocalizableString title, LocalizableString message)
{
return new DiagnosticDescriptor(
this._descriptorId, title, message,
DiagnosticCategory.Style,
DiagnosticSeverity.Hidden,
isEnabledByDefault: true);
}
#endregion
#region fixing
/// <summary>
/// Subclasses must implement this method to provide fixes for any diagnostics that this
/// type has registered. If this subclass wants the same code to run for this single
/// diagnostic as well as for when running fix-all, then it should call
/// <see cref="FixWithSyntaxEditorAsync"/> from its code action. This will end up calling
/// <see cref="FixAllAsync"/>, with that single <paramref name="diagnostic"/> in the
/// <see cref="ImmutableArray{T}"/> passed to that method.
/// </summary>
protected abstract Task<ImmutableArray<CodeAction>> ComputeCodeActionsAsync(
Document document, Diagnostic diagnostic, CancellationToken cancellationToken);
/// <summary>
/// Subclasses should implement this to support fixing all given diagnostics efficiently.
/// </summary>
protected abstract Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken);
protected Task<Document> FixWithSyntaxEditorAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
=> SyntaxEditorBasedCodeFixProvider.FixAllWithEditorAsync(
document, editor => FixAllAsync(document, ImmutableArray.Create(diagnostic), editor, cancellationToken), cancellationToken);
#endregion
#region refactoring
/// <summary>
/// Subclasses should implement this to provide their feature as a refactoring. This will
/// be called when the user has the code style set to 'refactoring only' (or if the
/// diagnostic is suppressed).
///
/// The implementation of this should offer all refactorings it can that are relevant at the
/// provided <paramref name="span"/>. Specifically, because these are just refactorings,
/// they should be offered when they would make the code match the desired user preference,
/// or even for allowing the user to quickly switch their code to *not* follow their desired
/// preference.
/// </summary>
protected abstract Task<ImmutableArray<CodeAction>> ComputeAllRefactoringsWhenAnalyzerInactiveAsync(
Document document, TextSpan span, CancellationToken cancellationToken);
/// <summary>
/// Subclasses should implement this to provide the refactoring that works in the opposing
/// direction of what the option preference is. This is only called if the user has the
/// code style enabled, and has it set to 'info/warning/error'. In this case it is the
/// *analyzer* responsible for making code compliant with the option.
///
/// The refactoring then exists to allow the user to update their code to go against that
/// option on an individual case by case basis.
///
/// For example, if the user had set that they want expression-bodies for methods (at
/// warning level), then this would offer 'use block body' on a method that had an
/// expression body already.
/// </summary>
protected abstract Task<ImmutableArray<CodeAction>> ComputeOpposingRefactoringsWhenAnalyzerActiveAsync(
Document document, TextSpan span, TOptionKind option, CancellationToken cancellationToken);
#endregion
}
}
......@@ -139,7 +139,8 @@ private async Task<Document> ConvertToClassAsync(Document document, TextSpan spa
var options = new CodeGenerationOptions(
generateMembers: true,
sortMembers: false,
autoInsertionLocation: false);
autoInsertionLocation: false,
parseOptions: root.SyntaxTree.Options);
return codeGenService.AddNamedType(
currentContainer, namedTypeSymbol, options, cancellationToken);
......
......@@ -496,7 +496,8 @@ private static bool InfoProbablyContainsTupleFieldNames(SyntaxTreeIndex info, Im
var options = new CodeGenerationOptions(
generateMembers: true,
sortMembers: false,
autoInsertionLocation: false);
autoInsertionLocation: false,
parseOptions: root.SyntaxTree.Options);
return codeGenService.AddNamedType(
currentContainer, namedTypeSymbol, options, cancellationToken);
......
......@@ -21,25 +21,18 @@ internal static class BlockSyntaxExtensions
if (preference != ExpressionBodyPreference.Never &&
block != null && block.Statements.Count == 1)
{
var version = ((CSharpParseOptions)options).LanguageVersion;
var acceptableVersion =
version >= LanguageVersion.CSharp7 ||
(version >= LanguageVersion.CSharp6 && IsSupportedInCSharp6(declarationKind));
var firstStatement = block.Statements[0];
if (acceptableVersion)
var version = ((CSharpParseOptions)options).LanguageVersion;
if (TryGetExpression(version, firstStatement, out expression, out semicolonToken) &&
MatchesPreference(expression, preference))
{
var firstStatement = block.Statements[0];
if (TryGetExpression(version, firstStatement, out expression, out semicolonToken) &&
MatchesPreference(expression, preference))
{
// The close brace of the block may have important trivia on it (like
// comments or directives). Preserve them on the semicolon when we
// convert to an expression body.
semicolonToken = semicolonToken.WithAppendedTrailingTrivia(
block.CloseBraceToken.LeadingTrivia.Where(t => !t.IsWhitespaceOrEndOfLine()));
return true;
}
// The close brace of the block may have important trivia on it (like
// comments or directives). Preserve them on the semicolon when we
// convert to an expression body.
semicolonToken = semicolonToken.WithAppendedTrailingTrivia(
block.CloseBraceToken.LeadingTrivia.Where(t => !t.IsWhitespaceOrEndOfLine()));
return true;
}
}
......@@ -54,11 +47,21 @@ internal static class BlockSyntaxExtensions
out ArrowExpressionClauseSyntax arrowExpression,
out SyntaxToken semicolonToken)
{
if (!block.TryConvertToExpressionBody(
var version = ((CSharpParseOptions)options).LanguageVersion;
// We can always use arrow-expression bodies in C# 7 or above.
// We can also use them in C# 6, but only a select set of member kinds.
var acceptableVersion =
version >= LanguageVersion.CSharp7 ||
(version >= LanguageVersion.CSharp6 && IsSupportedInCSharp6(declarationKind));
if (!acceptableVersion ||
!block.TryConvertToExpressionBody(
declarationKind, options, preference,
out var expression, out semicolonToken))
{
arrowExpression = default;
semicolonToken = default;
return false;
}
......
......@@ -34,7 +34,7 @@ public sealed override FixAllProvider GetFixAllProvider()
cancellationToken);
}
protected async Task<Document> FixAllWithEditorAsync(
internal static async Task<Document> FixAllWithEditorAsync(
Document document,
Func<SyntaxEditor, Task> editAsync,
CancellationToken cancellationToken)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册