Skip to content

Commit 3a76137

Browse files
authored
Merge branch 'main' into merge/release/10.0.2xx-to-main
2 parents 0297e5b + c401b59 commit 3a76137

25 files changed

+1878
-6
lines changed

github-merge-flow.jsonc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
"release/10.0.2xx":{
3636
"MergeToBranch": "main",
3737
"ExtraSwitches": "-QuietComments"
38+
},
39+
// Automate opening PRs to merge sdk repos from dnup to release/dnup
40+
"dnup":{
41+
"MergeToBranch": "release/dnup",
42+
"ExtraSwitches": "-QuietComments"
3843
}
3944
}
4045
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System.Collections.Immutable;
4+
using System.Composition;
5+
using Analyzer.Utilities;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CodeActions;
8+
using Microsoft.CodeAnalysis.CodeFixes;
9+
using Microsoft.CodeAnalysis.CSharp;
10+
using Microsoft.CodeAnalysis.CSharp.Syntax;
11+
using Microsoft.NetCore.Analyzers;
12+
using Microsoft.NetCore.Analyzers.Performance;
13+
14+
namespace Microsoft.NetCore.CSharp.Analyzers.Performance
15+
{
16+
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
17+
public sealed class CSharpCollapseMultiplePathOperationsFixer : CodeFixProvider
18+
{
19+
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(CollapseMultiplePathOperationsAnalyzer.RuleId);
20+
21+
public override FixAllProvider GetFixAllProvider()
22+
=> WellKnownFixAllProviders.BatchFixer;
23+
24+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
25+
{
26+
var document = context.Document;
27+
var diagnostic = context.Diagnostics[0];
28+
var root = await document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
29+
var node = root.FindNode(context.Span, getInnermostNodeForTie: true);
30+
31+
if (node is not InvocationExpressionSyntax invocation ||
32+
await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false) is not { } semanticModel ||
33+
semanticModel.Compilation.GetTypeByMetadataName(WellKnownTypeNames.SystemIOPath) is not { } pathType)
34+
{
35+
return;
36+
}
37+
38+
// Get the method name from diagnostic properties
39+
if (!diagnostic.Properties.TryGetValue(CollapseMultiplePathOperationsAnalyzer.MethodNameKey, out var methodName))
40+
{
41+
methodName = "Path";
42+
}
43+
44+
context.RegisterCodeFix(
45+
CodeAction.Create(
46+
string.Format(MicrosoftNetCoreAnalyzersResources.CollapseMultiplePathOperationsCodeFixTitle, methodName),
47+
createChangedDocument: cancellationToken => CollapsePathOperationAsync(document, root, invocation, pathType, semanticModel, cancellationToken),
48+
equivalenceKey: nameof(MicrosoftNetCoreAnalyzersResources.CollapseMultiplePathOperationsCodeFixTitle)),
49+
diagnostic);
50+
}
51+
52+
private static Task<Document> CollapsePathOperationAsync(Document document, SyntaxNode root, InvocationExpressionSyntax invocation, INamedTypeSymbol pathType, SemanticModel semanticModel, CancellationToken cancellationToken)
53+
{
54+
// Collect all arguments by recursively unwrapping nested Path.Combine/Join calls
55+
var allArguments = CollectAllArguments(invocation, pathType, semanticModel);
56+
57+
// Create new argument list with all collected arguments
58+
var newArgumentList = SyntaxFactory.ArgumentList(
59+
SyntaxFactory.SeparatedList(allArguments));
60+
61+
// Create the new invocation with all arguments
62+
var newInvocation = invocation.WithArgumentList(newArgumentList)
63+
.WithTriviaFrom(invocation);
64+
65+
var newRoot = root.ReplaceNode(invocation, newInvocation);
66+
67+
return Task.FromResult(document.WithSyntaxRoot(newRoot));
68+
}
69+
70+
private static ArgumentSyntax[] CollectAllArguments(InvocationExpressionSyntax invocation, INamedTypeSymbol pathType, SemanticModel semanticModel)
71+
{
72+
var arguments = ImmutableArray.CreateBuilder<ArgumentSyntax>();
73+
74+
foreach (var argument in invocation.ArgumentList.Arguments)
75+
{
76+
if (argument.Expression is InvocationExpressionSyntax nestedInvocation &&
77+
IsPathCombineOrJoin(nestedInvocation, pathType, semanticModel, out var methodName) &&
78+
IsPathCombineOrJoin(invocation, pathType, semanticModel, out var outerMethodName) &&
79+
methodName == outerMethodName)
80+
{
81+
// Recursively collect arguments from nested invocation
82+
arguments.AddRange(CollectAllArguments(nestedInvocation, pathType, semanticModel));
83+
}
84+
else
85+
{
86+
arguments.Add(argument);
87+
}
88+
}
89+
90+
return arguments.ToArray();
91+
}
92+
93+
private static bool IsPathCombineOrJoin(InvocationExpressionSyntax invocation, INamedTypeSymbol pathType, SemanticModel semanticModel, out string methodName)
94+
{
95+
if (semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol &&
96+
SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, pathType) &&
97+
methodSymbol.Name is "Combine" or "Join")
98+
{
99+
methodName = methodSymbol.Name;
100+
return true;
101+
}
102+
103+
methodName = string.Empty;
104+
return false;
105+
}
106+
}
107+
}

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,6 +1920,18 @@ Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' s
19201920
|CodeFix|False|
19211921
---
19221922

1923+
## [CA1877](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1877): Collapse consecutive Path.Combine or Path.Join operations
1924+
1925+
When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability.
1926+
1927+
|Item|Value|
1928+
|-|-|
1929+
|Category|Performance|
1930+
|Enabled|True|
1931+
|Severity|Info|
1932+
|CodeFix|True|
1933+
---
1934+
19231935
## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope
19241936

19251937
If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead.
@@ -2178,6 +2190,18 @@ JsonDocument implements IDisposable and needs to be properly disposed. When only
21782190
|CodeFix|True|
21792191
---
21802192

2193+
## [CA2027](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2027): Cancel Task.Delay after Task.WhenAny completes
2194+
2195+
When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes.
2196+
2197+
|Item|Value|
2198+
|-|-|
2199+
|Category|Reliability|
2200+
|Enabled|True|
2201+
|Severity|Info|
2202+
|CodeFix|False|
2203+
---
2204+
21812205
## [CA2100](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100): Review SQL queries for security vulnerabilities
21822206

21832207
SQL queries that directly use user input can be vulnerable to SQL injection attacks. Review this SQL query for potential vulnerabilities, and consider using a parameterized SQL query.

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3527,6 +3527,26 @@
35273527
]
35283528
}
35293529
},
3530+
"CA1877": {
3531+
"id": "CA1877",
3532+
"shortDescription": "Collapse consecutive Path.Combine or Path.Join operations",
3533+
"fullDescription": "When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability.",
3534+
"defaultLevel": "note",
3535+
"helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1877",
3536+
"properties": {
3537+
"category": "Performance",
3538+
"isEnabledByDefault": true,
3539+
"typeName": "CollapseMultiplePathOperationsAnalyzer",
3540+
"languages": [
3541+
"C#",
3542+
"Visual Basic"
3543+
],
3544+
"tags": [
3545+
"Telemetry",
3546+
"EnabledRuleInAggressiveMode"
3547+
]
3548+
}
3549+
},
35303550
"CA2000": {
35313551
"id": "CA2000",
35323552
"shortDescription": "Dispose objects before losing scope",
@@ -3890,6 +3910,26 @@
38903910
]
38913911
}
38923912
},
3913+
"CA2027": {
3914+
"id": "CA2027",
3915+
"shortDescription": "Cancel Task.Delay after Task.WhenAny completes",
3916+
"fullDescription": "When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes.",
3917+
"defaultLevel": "note",
3918+
"helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2027",
3919+
"properties": {
3920+
"category": "Reliability",
3921+
"isEnabledByDefault": true,
3922+
"typeName": "DoNotUseNonCancelableTaskDelayWithWhenAny",
3923+
"languages": [
3924+
"C#",
3925+
"Visual Basic"
3926+
],
3927+
"tags": [
3928+
"Telemetry",
3929+
"EnabledRuleInAggressiveMode"
3930+
]
3931+
}
3932+
},
38933933
"CA2100": {
38943934
"id": "CA2100",
38953935
"shortDescription": "Review SQL queries for security vulnerabilities",

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer,
88
CA1874 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1874)
99
CA1875 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1875)
1010
CA1876 | Performance | Info | DoNotUseAsParallelInForEachLoopAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876)
11+
CA1877 | Performance | Info | CollapseMultiplePathOperationsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/cA1877)
1112
CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023)
1213
CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024)
1314
CA2025 | Reliability | Disabled | DoNotPassDisposablesIntoUnawaitedTasksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2025)
1415
CA2026 | Reliability | Info | PreferJsonElementParse, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2026)
16+
CA2027 | Reliability | Info | DoNotUseNonCancelableTaskDelayWithWhenAny, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2027)

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,15 @@
16461646
<data name="DoNotUseWhenAllWithSingleTaskFix" xml:space="preserve">
16471647
<value>Replace 'WhenAll' call with argument</value>
16481648
</data>
1649+
<data name="DoNotUseNonCancelableTaskDelayWithWhenAnyTitle" xml:space="preserve">
1650+
<value>Cancel Task.Delay after Task.WhenAny completes</value>
1651+
</data>
1652+
<data name="DoNotUseNonCancelableTaskDelayWithWhenAnyMessage" xml:space="preserve">
1653+
<value>Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources</value>
1654+
</data>
1655+
<data name="DoNotUseNonCancelableTaskDelayWithWhenAnyDescription" xml:space="preserve">
1656+
<value>When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes.</value>
1657+
</data>
16491658
<data name="UseStringEqualsOverStringCompareCodeFixTitle" xml:space="preserve">
16501659
<value>Use 'string.Equals'</value>
16511660
</data>
@@ -2240,4 +2249,16 @@ Widening and user defined conversions are not supported with generic types.</val
22402249
<data name="DoNotUseThreadVolatileReadWriteCodeFixTitle" xml:space="preserve">
22412250
<value>Replace obsolete call</value>
22422251
</data>
2252+
<data name="CollapseMultiplePathOperationsTitle" xml:space="preserve">
2253+
<value>Collapse consecutive Path.Combine or Path.Join operations</value>
2254+
</data>
2255+
<data name="CollapseMultiplePathOperationsMessage" xml:space="preserve">
2256+
<value>Multiple consecutive Path.{0} operations can be collapsed into a single operation</value>
2257+
</data>
2258+
<data name="CollapseMultiplePathOperationsDescription" xml:space="preserve">
2259+
<value>When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability.</value>
2260+
</data>
2261+
<data name="CollapseMultiplePathOperationsCodeFixTitle" xml:space="preserve">
2262+
<value>Collapse into single Path.{0} operation</value>
2263+
</data>
22432264
</root>

0 commit comments

Comments
 (0)