Skip to content

Commit 0297e5b

Browse files
authored
Merge branch 'main' into merge/release/10.0.2xx-to-main
2 parents 0e380a0 + 448facf commit 0297e5b

25 files changed

+886
-89
lines changed

src/Layout/redist/targets/GenerateBundledVersions.targets

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,21 @@
252252
android-x64;
253253
" />
254254

255+
<Net110RuntimePackRids Include="
256+
@(Net100RuntimePackRids);
257+
ios-arm64;
258+
iossimulator-arm64;
259+
iossimulator-x64;
260+
tvos-arm64;
261+
tvossimulator-arm64;
262+
tvossimulator-x64;
263+
maccatalyst-x64;
264+
maccatalyst-arm64;
265+
" />
266+
255267
<NetCoreAppHostRids Include="@(Net90AppHostRids)" />
256268

257-
<NetCoreRuntimePackRids Include="@(Net100RuntimePackRids)" />
269+
<NetCoreRuntimePackRids Include="@(Net110RuntimePackRids)" />
258270

259271
<!--
260272
In source-only builds, we build the current RID from source, which may be
@@ -692,7 +704,7 @@ Copyright (c) .NET Foundation. All rights reserved.
692704
TargetingPackName="Microsoft.NETCore.App.Ref"
693705
TargetingPackVersion="$(MicrosoftNETCoreAppRefPackageVersion)"
694706
RuntimePackNamePatterns="Microsoft.NETCore.App.Runtime.**RID**"
695-
RuntimePackRuntimeIdentifiers="@(NetCoreRuntimePackRids, '%3B')"
707+
RuntimePackRuntimeIdentifiers="@(Net100RuntimePackRids, '%3B')"
696708
/>
697709
698710
<KnownAppHostPack Include="Microsoft.NETCore.App"

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1358,7 +1358,7 @@ Enumerable.Count() potentially enumerates the sequence while a Length/Count prop
13581358

13591359
## [CA1830](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1830): Prefer strongly-typed Append and Insert method overloads on StringBuilder
13601360

1361-
StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload.
1361+
StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)).
13621362

13631363
|Item|Value|
13641364
|-|-|
@@ -1908,6 +1908,18 @@ In many situations, logging is disabled or set to a log level that results in an
19081908
|CodeFix|True|
19091909
---
19101910

1911+
## [CA1876](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876): Do not use 'AsParallel' in 'foreach'
1912+
1913+
Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead.
1914+
1915+
|Item|Value|
1916+
|-|-|
1917+
|Category|Performance|
1918+
|Enabled|True|
1919+
|Severity|Info|
1920+
|CodeFix|False|
1921+
---
1922+
19111923
## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope
19121924

19131925
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.

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2789,7 +2789,7 @@
27892789
"CA1830": {
27902790
"id": "CA1830",
27912791
"shortDescription": "Prefer strongly-typed Append and Insert method overloads on StringBuilder",
2792-
"fullDescription": "StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload.",
2792+
"fullDescription": "StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)).",
27932793
"defaultLevel": "note",
27942794
"helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1830",
27952795
"properties": {
@@ -3507,6 +3507,26 @@
35073507
]
35083508
}
35093509
},
3510+
"CA1876": {
3511+
"id": "CA1876",
3512+
"shortDescription": "Do not use 'AsParallel' in 'foreach'",
3513+
"fullDescription": "Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead.",
3514+
"defaultLevel": "note",
3515+
"helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876",
3516+
"properties": {
3517+
"category": "Performance",
3518+
"isEnabledByDefault": true,
3519+
"typeName": "DoNotUseAsParallelInForEachLoopAnalyzer",
3520+
"languages": [
3521+
"C#",
3522+
"Visual Basic"
3523+
],
3524+
"tags": [
3525+
"Telemetry",
3526+
"EnabledRuleInAggressiveMode"
3527+
]
3528+
}
3529+
},
35103530
"CA2000": {
35113531
"id": "CA2000",
35123532
"shortDescription": "Dispose objects before losing scope",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Rule ID | Category | Severity | Notes
77
CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
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)
10+
CA1876 | Performance | Info | DoNotUseAsParallelInForEachLoopAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876)
1011
CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023)
1112
CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024)
1213
CA2025 | Reliability | Disabled | DoNotPassDisposablesIntoUnawaitedTasksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2025)

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,15 @@
195195
<data name="DoNotUseCountWhenAnyCanBeUsedTitle" xml:space="preserve">
196196
<value>Do not use Count() or LongCount() when Any() can be used</value>
197197
</data>
198+
<data name="DoNotUseAsParallelInForEachLoopTitle" xml:space="preserve">
199+
<value>Do not use 'AsParallel' in 'foreach'</value>
200+
</data>
201+
<data name="DoNotUseAsParallelInForEachLoopMessage" xml:space="preserve">
202+
<value>Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized</value>
203+
</data>
204+
<data name="DoNotUseAsParallelInForEachLoopDescription" xml:space="preserve">
205+
<value>Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead.</value>
206+
</data>
198207
<data name="PreferConvertToHexStringOverBitConverterTitle" xml:space="preserve">
199208
<value>Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString'</value>
200209
</data>
@@ -1287,14 +1296,17 @@
12871296
<value>Prefer strongly-typed Append and Insert method overloads on StringBuilder</value>
12881297
</data>
12891298
<data name="PreferTypedStringBuilderAppendOverloadsDescription" xml:space="preserve">
1290-
<value>StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload.</value>
1299+
<value>StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)).</value>
12911300
</data>
12921301
<data name="PreferTypedStringBuilderAppendOverloadsMessage" xml:space="preserve">
1293-
<value>Remove the ToString call in order to use a strongly-typed StringBuilder overload</value>
1302+
<value>Prefer strongly-typed StringBuilder overload</value>
12941303
</data>
12951304
<data name="PreferTypedStringBuilderAppendOverloadsRemoveToString" xml:space="preserve">
12961305
<value>Remove the ToString call</value>
12971306
</data>
1307+
<data name="PreferTypedStringBuilderAppendOverloadsReplaceStringConstructor" xml:space="preserve">
1308+
<value>Use StringBuilder.Append(char, int) overload</value>
1309+
</data>
12981310
<data name="PreferStringContainsOverIndexOfDescription" xml:space="preserve">
12991311
<value>Calls to 'string.IndexOf' where the result is used to check for the presence/absence of a substring can be replaced by 'string.Contains'.</value>
13001312
</data>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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.Linq;
5+
using Analyzer.Utilities;
6+
using Analyzer.Utilities.Extensions;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
using Microsoft.CodeAnalysis.Operations;
10+
11+
namespace Microsoft.NetCore.Analyzers.Performance
12+
{
13+
using static MicrosoftNetCoreAnalyzersResources;
14+
15+
/// <summary>
16+
/// CA1876: <inheritdoc cref="DoNotUseAsParallelInForEachLoopTitle"/>
17+
/// Analyzer to detect misuse of AsParallel() when used directly in a foreach loop.
18+
/// </summary>
19+
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
20+
public sealed class DoNotUseAsParallelInForEachLoopAnalyzer : DiagnosticAnalyzer
21+
{
22+
internal const string RuleId = "CA1876";
23+
24+
internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
25+
RuleId,
26+
CreateLocalizableResourceString(nameof(DoNotUseAsParallelInForEachLoopTitle)),
27+
CreateLocalizableResourceString(nameof(DoNotUseAsParallelInForEachLoopMessage)),
28+
DiagnosticCategory.Performance,
29+
RuleLevel.IdeSuggestion,
30+
description: CreateLocalizableResourceString(nameof(DoNotUseAsParallelInForEachLoopDescription)),
31+
isPortedFxCopRule: false,
32+
isDataflowRule: false);
33+
34+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
35+
36+
public override void Initialize(AnalysisContext context)
37+
{
38+
context.EnableConcurrentExecution();
39+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
40+
context.RegisterCompilationStartAction(OnCompilationStart);
41+
}
42+
43+
private static void OnCompilationStart(CompilationStartAnalysisContext context)
44+
{
45+
var typeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation);
46+
47+
// Get the ParallelEnumerable type
48+
var parallelEnumerableType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemLinqParallelEnumerable);
49+
50+
if (parallelEnumerableType == null)
51+
{
52+
return;
53+
}
54+
55+
// Get all AsParallel methods - use SymbolEqualityComparer for proper comparison
56+
var asParallelMethods = ImmutableHashSet.CreateRange<IMethodSymbol>(
57+
SymbolEqualityComparer.Default,
58+
parallelEnumerableType.GetMembers("AsParallel").OfType<IMethodSymbol>());
59+
60+
if (asParallelMethods.IsEmpty)
61+
{
62+
return;
63+
}
64+
65+
context.RegisterOperationAction(ctx => AnalyzeForEachLoop(ctx, asParallelMethods), OperationKind.Loop);
66+
}
67+
68+
private static void AnalyzeForEachLoop(OperationAnalysisContext context, ImmutableHashSet<IMethodSymbol> asParallelMethods)
69+
{
70+
if (context.Operation is not IForEachLoopOperation forEachLoop)
71+
{
72+
return;
73+
}
74+
75+
// Check if the collection is a direct result of AsParallel()
76+
var collection = forEachLoop.Collection;
77+
78+
// Walk up conversions to find the actual operation
79+
while (collection is IConversionOperation conversion)
80+
{
81+
collection = conversion.Operand;
82+
}
83+
84+
// Check if this is an invocation of AsParallel
85+
if (collection is IInvocationOperation invocation)
86+
{
87+
var targetMethod = invocation.TargetMethod;
88+
89+
// For extension methods, we need to check the ReducedFrom or the original method
90+
var methodToCheck = targetMethod.ReducedFrom ?? targetMethod;
91+
92+
if (asParallelMethods.Contains(methodToCheck.OriginalDefinition))
93+
{
94+
// Report diagnostic on the AsParallel call
95+
context.ReportDiagnostic(invocation.CreateDiagnostic(Rule));
96+
}
97+
}
98+
}
99+
}
100+
}

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.Fixer.cs

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,59 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
2929
SyntaxNode root = await doc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
3030
if (root.FindNode(context.Span) is SyntaxNode expression)
3131
{
32-
string title = MicrosoftNetCoreAnalyzersResources.PreferTypedStringBuilderAppendOverloadsRemoveToString;
33-
context.RegisterCodeFix(
34-
CodeAction.Create(title,
35-
async ct =>
36-
{
37-
SemanticModel model = await doc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
38-
if (model.GetOperationWalkingUpParentChain(expression, cancellationToken) is IArgumentOperation arg &&
39-
arg.Value is IInvocationOperation invoke &&
40-
invoke.Instance?.Syntax is SyntaxNode replacement)
32+
SemanticModel model = await doc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
33+
var operation = model.GetOperationWalkingUpParentChain(expression, cancellationToken);
34+
35+
// Handle ToString() case
36+
if (operation is IArgumentOperation arg &&
37+
arg.Value is IInvocationOperation invoke &&
38+
invoke.Instance?.Syntax is SyntaxNode replacement)
39+
{
40+
string title = MicrosoftNetCoreAnalyzersResources.PreferTypedStringBuilderAppendOverloadsRemoveToString;
41+
context.RegisterCodeFix(
42+
CodeAction.Create(title,
43+
async ct =>
4144
{
4245
DocumentEditor editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false);
4346
editor.ReplaceNode(expression, editor.Generator.Argument(replacement));
4447
return editor.GetChangedDocument();
45-
}
48+
},
49+
equivalenceKey: title),
50+
context.Diagnostics);
51+
}
52+
// Handle new string(char, int) case (only for Append, not Insert)
53+
else if (operation is IArgumentOperation argOp &&
54+
argOp.Value is IObjectCreationOperation objectCreation &&
55+
objectCreation.Arguments.Length == 2 &&
56+
argOp.Parent is IInvocationOperation invocationOp &&
57+
invocationOp.TargetMethod.Name == "Append")
58+
{
59+
string title = MicrosoftNetCoreAnalyzersResources.PreferTypedStringBuilderAppendOverloadsReplaceStringConstructor;
60+
context.RegisterCodeFix(
61+
CodeAction.Create(title,
62+
async ct =>
63+
{
64+
DocumentEditor editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false);
4665

47-
return doc;
48-
},
49-
equivalenceKey: title),
50-
context.Diagnostics);
66+
// Get the char and int arguments from the string constructor
67+
var charArgSyntax = objectCreation.Arguments[0].Value.Syntax;
68+
var intArgSyntax = objectCreation.Arguments[1].Value.Syntax;
69+
70+
// Append(new string(c, count)) -> Append(c, count)
71+
SyntaxNode newInvocation = editor.Generator.InvocationExpression(
72+
editor.Generator.MemberAccessExpression(
73+
invocationOp.Instance!.Syntax,
74+
"Append"),
75+
editor.Generator.Argument(charArgSyntax),
76+
editor.Generator.Argument(intArgSyntax));
77+
78+
editor.ReplaceNode(invocationOp.Syntax, newInvocation);
79+
return editor.GetChangedDocument();
80+
},
81+
equivalenceKey: title),
82+
context.Diagnostics);
83+
}
5184
}
5285
}
5386
}
54-
}
87+
}

0 commit comments

Comments
 (0)