Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -2,7 +2,6 @@
// 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.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
Expand All @@ -26,18 +25,15 @@ namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryLambdaExpression;
/// time.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer : AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer
internal sealed class CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer()
: AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(
IDEDiagnosticIds.RemoveUnnecessaryLambdaExpressionDiagnosticId,
EnforceOnBuildValues.RemoveUnnecessaryLambdaExpression,
CSharpCodeStyleOptions.PreferMethodGroupConversion,
fadingOption: null,
new LocalizableResourceString(nameof(CSharpAnalyzersResources.Remove_unnecessary_lambda_expression), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)),
new LocalizableResourceString(nameof(CSharpAnalyzersResources.Lambda_expression_can_be_removed), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
public CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer()
: base(IDEDiagnosticIds.RemoveUnnecessaryLambdaExpressionDiagnosticId,
EnforceOnBuildValues.RemoveUnnecessaryLambdaExpression,
CSharpCodeStyleOptions.PreferMethodGroupConversion,
fadingOption: null,
new LocalizableResourceString(nameof(CSharpAnalyzersResources.Remove_unnecessary_lambda_expression), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)),
new LocalizableResourceString(nameof(CSharpAnalyzersResources.Lambda_expression_can_be_removed), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
}

public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;

Expand All @@ -51,7 +47,7 @@ protected override void InitializeWorker(AnalysisContext context)
var conditionalAttributeType = context.Compilation.ConditionalAttribute();

context.RegisterSyntaxNodeAction(
c => AnalyzeSyntax(c, expressionType, conditionalAttributeType),
context => AnalyzeSyntax(context, expressionType, conditionalAttributeType),
SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.AnonymousMethodExpression);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryLambdaExpression;

Expand All @@ -39,7 +43,9 @@ protected override Task FixAllAsync(
{
foreach (var diagnostic in diagnostics)
{
var anonymousFunction = diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken);
var anonymousFunction = diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken) as AnonymousFunctionExpressionSyntax;
if (anonymousFunction is null)
continue;

editor.ReplaceNode(anonymousFunction,
(current, generator) =>
Expand All @@ -52,8 +58,32 @@ protected override Task FixAllAsync(

return current;
});

// If the inner invocation has important trivia on it, move it to the container of the anonymous function.
if (TryGetAnonymousFunctionInvocation(anonymousFunction, out var invocation, out _) &&
invocation.GetLeadingTrivia().Any(t => t.IsSingleOrMultiLineComment()))
{
var containingStatement = anonymousFunction.AncestorsAndSelf().OfType<StatementSyntax>().FirstOrDefault();
if (containingStatement != null)
{
editor.ReplaceNode(containingStatement,
(current, generator) => current
.WithPrependedLeadingTrivia(TakeComments(invocation.GetLeadingTrivia()))
.WithAdditionalAnnotations(Formatter.Annotation));
}
}
}

return Task.CompletedTask;
}

private static IEnumerable<SyntaxTrivia> TakeComments(SyntaxTriviaList triviaList)
{
var lastComment = triviaList.Last(t => t.IsSingleOrMultiLineComment());
var lastIndex = triviaList.IndexOf(lastComment) + 1;
if (lastIndex < triviaList.Count && triviaList[lastIndex].IsEndOfLine())
lastIndex++;

return triviaList.Take(lastIndex);
Copy link
Member

Choose a reason for hiding this comment

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

didn't quite get why we need this filtering at a glance - mind adding a comment on it?

Copy link
Member

Choose a reason for hiding this comment

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

seems like its trying to get all the trivia up to the last comment and new line - but not sure why we aren't taking all the trivia?

Copy link
Member Author

Choose a reason for hiding this comment

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

the idea is that we want to grab up through the last comment and the newline that follows and only move those over if it exists. we don't want to generally move things like when the user only has blank lines above the inner call. i will doc :)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.CodeStyle;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -19,11 +20,11 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnnecessaryLambda
CSharpRemoveUnnecessaryLambdaExpressionCodeFixProvider>;

[Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)]
public class RemoveUnnecessaryLambdaExpressionTests
public sealed class RemoveUnnecessaryLambdaExpressionTests
{
private static async Task TestInRegularAndScriptAsync(
string testCode,
string fixedCode,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string testCode,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string fixedCode,
LanguageVersion version = LanguageVersion.CSharp12,
OutputKind? outputKind = null)
{
Expand All @@ -40,7 +41,7 @@ private static async Task TestInRegularAndScriptAsync(
}

private static Task TestMissingInRegularAndScriptAsync(
string testCode,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string testCode,
LanguageVersion version = LanguageVersion.CSharp12,
OutputKind? outputKind = null)
=> TestInRegularAndScriptAsync(testCode, testCode, version, outputKind);
Expand Down Expand Up @@ -2035,4 +2036,44 @@ public string ToStr(int x)
}
""");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71300")]
public async Task PreserveComment()
{
await TestInRegularAndScriptAsync("""
using System;

class C
{
void M1()
{
M2([|() =>
{
// I hope M2 doesn't call M1!
|]M1();
});
}

void M2(Action a)
{
}
}
""",
"""
using System;

class C
{
void M1()
{
// I hope M2 doesn't call M1!
M2(M1);
}

void M2(Action a)
{
}
}
""");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,9 @@ void M()

// SingleLine doc comment with '\r' line separators
await TestAsync("""
///<summary>Hello! ///</summary> class C { void M() { $$C obj; } }
///<summary>Hello!
///</summary>
class C { void M() { $$C obj; } }
""",
MainDescription("class C"),
Documentation("Hello!"));
Expand Down Expand Up @@ -632,7 +634,12 @@ void M()

// Multiline doc comment with '\r' line separators
await TestAsync("""
/** * <summary> * Hello! * </summary> */ class C { void M() { $$C obj; } }
/**
* <summary>
* Hello!
* </summary>
*/
class C { void M() { $$C obj; } }
""",
MainDescription("class C"),
Documentation("Hello!"));
Expand Down
Loading