Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8011355

Browse files
authored
Add analyzer/fixer to suggest using cached SearchValues instances (#6898)
* Initial Analyzer implementation* Code fixer* Add support for string.IndexOfAny(char[])* Catch simple cases of array element modification* Use built-in helper instead of Linq* Also detect field assignments in ctor* Move system namespace import to helper method* Replace array creations wtih string literals* Add support for more property getter patterns* Simplify test helper* Revert Utf8String support :(* Update tests* msbuild /t:pack /v:m* Fix editor renaming* Exclude string uses on a conditional access* Add test for array field with const char reference* Add back Utf8String support* Update messages/descriptions* Add support for field initialized from literal.ToCharArray* More tests for ToCharArray* Better handle member names that start with _* Avoid some duplication between Syntax and Operation analysis* Fix top-level statements and add logic to remove unused members* ImmutableHashSet, no OfType* Remove some duplication* Turn one analyzer test into code fixer tests* Shorten analyzer title
1 parente5959ba commit8011355

File tree

29 files changed

+2480
-2
lines changed

29 files changed

+2480
-2
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
usingSystem;
4+
usingSystem.Collections.Generic;
5+
usingSystem.Diagnostics;
6+
usingSystem.Threading;
7+
usingSystem.Threading.Tasks;
8+
usingAnalyzer.Utilities.Extensions;
9+
usingMicrosoft.CodeAnalysis;
10+
usingMicrosoft.CodeAnalysis.CodeActions;
11+
usingMicrosoft.CodeAnalysis.CodeFixes;
12+
usingMicrosoft.CodeAnalysis.CSharp;
13+
usingMicrosoft.CodeAnalysis.CSharp.Syntax;
14+
usingMicrosoft.CodeAnalysis.Operations;
15+
usingMicrosoft.NetCore.Analyzers.Performance;
16+
17+
namespaceMicrosoft.NetCore.CSharp.Analyzers.Performance
18+
{
19+
/// <inheritdoc/>
20+
[ExportCodeFixProvider(LanguageNames.CSharp)]
21+
publicsealedclassCSharpUseSearchValuesFixer:UseSearchValuesFixer
22+
{
23+
protectedoverrideasyncValueTask<(SyntaxNodeTypeDeclaration,INamedTypeSymbol?TypeSymbol,boolIsRealType)>GetTypeSymbolAsync(SemanticModelsemanticModel,SyntaxNodenode,CancellationTokencancellationToken)
24+
{
25+
SyntaxNode?typeDeclarationOrCompilationUnit=node.FirstAncestorOrSelf<TypeDeclarationSyntax>();
26+
27+
typeDeclarationOrCompilationUnit??=awaitnode.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
28+
29+
returntypeDeclarationOrCompilationUnitisTypeDeclarationSyntaxtypeDeclaration
30+
?(typeDeclaration,semanticModel.GetDeclaredSymbol(typeDeclaration,cancellationToken),IsRealType:true)
31+
:(typeDeclarationOrCompilationUnit,semanticModel.GetDeclaredSymbol((CompilationUnitSyntax)typeDeclarationOrCompilationUnit,cancellationToken)?.ContainingType,IsRealType:false);
32+
}
33+
34+
protectedoverrideSyntaxNodeReplaceSearchValuesFieldName(SyntaxNodenode)
35+
{
36+
if(nodeisFieldDeclarationSyntaxfieldDeclaration&&
37+
fieldDeclaration.Declarationis{}declaration&&
38+
declaration.Variablesis[vardeclarator])
39+
{
40+
varnewDeclarator=declarator.ReplaceToken(declarator.Identifier,declarator.Identifier.WithAdditionalAnnotations(RenameAnnotation.Create()));
41+
returnfieldDeclaration.WithDeclaration(declaration.WithVariables(newSeparatedSyntaxList<VariableDeclaratorSyntax>().Add(newDeclarator)));
42+
}
43+
44+
returnnode;
45+
}
46+
47+
protectedoverrideSyntaxNodeGetDeclaratorInitializer(SyntaxNodesyntax)
48+
{
49+
if(syntaxisVariableDeclaratorSyntaxvariableDeclarator)
50+
{
51+
returnvariableDeclarator.Initializer!.Value;
52+
}
53+
54+
if(syntaxisPropertyDeclarationSyntaxpropertyDeclaration)
55+
{
56+
returnCSharpUseSearchValuesAnalyzer.TryGetPropertyGetterExpression(propertyDeclaration)!;
57+
}
58+
59+
thrownewInvalidOperationException($"Expected 'VariableDeclaratorSyntax' or 'PropertyDeclarationSyntax', got{syntax.GetType().Name}");
60+
}
61+
62+
// new[] { 'a', 'b', 'c' } => "abc"
63+
// new[] { (byte)'a', (byte)'b', (byte)'c' } => "abc"u8
64+
// "abc".ToCharArray() => "abc"
65+
protectedoverrideSyntaxNode?TryReplaceArrayCreationWithInlineLiteralExpression(IOperationoperation)
66+
{
67+
if(operationisIConversionOperationconversion)
68+
{
69+
operation=conversion.Operand;
70+
}
71+
72+
if(operationisIArrayCreationOperationarrayCreation&&
73+
arrayCreation.GetElementType()is{}elementType)
74+
{
75+
boolisByte=elementType.SpecialType==SpecialType.System_Byte;
76+
77+
if(isByte&&
78+
(operation.SemanticModel?.Compilationis notCSharpCompilationcompilation||
79+
compilation.LanguageVersion<(LanguageVersion)1100))// LanguageVersion.CSharp11
80+
{
81+
// Can't use Utf8StringLiterals
82+
returnnull;
83+
}
84+
85+
List<char>values=new();
86+
87+
if(arrayCreation.SyntaxisExpressionSyntaxcreationSyntax&&
88+
CSharpUseSearchValuesAnalyzer.IsConstantByteOrCharArrayCreationExpression(operation.SemanticModel!,creationSyntax,values,out_)&&
89+
values.Count<=128&&// Arbitrary limit to avoid emitting huge literals
90+
!ContainsAnyComments(creationSyntax))// Avoid removing potentially valuable comments
91+
{
92+
stringvaluesString=string.Concat(values);
93+
stringstringLiteral=SymbolDisplay.FormatLiteral(valuesString,quote:true);
94+
95+
constSyntaxKindUtf8StringLiteralExpression=(SyntaxKind)8756;
96+
constSyntaxKindUtf8StringLiteralToken=(SyntaxKind)8520;
97+
98+
returnSyntaxFactory.LiteralExpression(
99+
isByte?Utf8StringLiteralExpression:SyntaxKind.StringLiteralExpression,
100+
SyntaxFactory.Token(
101+
leading:default,
102+
kind:isByte?Utf8StringLiteralToken:SyntaxKind.StringLiteralToken,
103+
text:isByte?$"{stringLiteral}u8":stringLiteral,
104+
valueText:valuesString,
105+
trailing:default));
106+
}
107+
}
108+
elseif(operationisIInvocationOperationinvocation)
109+
{
110+
if(UseSearchValuesAnalyzer.IsConstantStringToCharArrayInvocation(invocation,out_))
111+
{
112+
Debug.Assert(invocation.Instanceis notnull);
113+
returninvocation.Instance!.Syntax;
114+
}
115+
}
116+
117+
returnnull;
118+
}
119+
120+
privatestaticboolContainsAnyComments(SyntaxNodenode)
121+
{
122+
foreach(SyntaxTriviatriviainnode.DescendantTrivia(node.Span))
123+
{
124+
if(trivia.Kind()isSyntaxKind.SingleLineCommentTrivia orSyntaxKind.MultiLineCommentTrivia)
125+
{
126+
returntrue;
127+
}
128+
}
129+
130+
returnfalse;
131+
}
132+
}
133+
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
usingSystem.Collections.Generic;
4+
usingMicrosoft.CodeAnalysis;
5+
usingMicrosoft.CodeAnalysis.CSharp;
6+
usingMicrosoft.CodeAnalysis.CSharp.Syntax;
7+
usingMicrosoft.CodeAnalysis.Diagnostics;
8+
usingMicrosoft.CodeAnalysis.Operations;
9+
usingMicrosoft.NetCore.Analyzers.Performance;
10+
11+
namespaceMicrosoft.NetCore.CSharp.Analyzers.Performance
12+
{
13+
/// <inheritdoc/>
14+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
15+
publicsealedclassCSharpUseSearchValuesAnalyzer:UseSearchValuesAnalyzer
16+
{
17+
// char[] myField = new char[] { 'a', 'b', 'c' };
18+
// char[] myField = new[] { 'a', 'b', 'c' };
19+
// char[] myField = "abc".ToCharArray();
20+
// char[] myField = ConstString.ToCharArray();
21+
// byte[] myField = new[] { (byte)'a', (byte)'b', (byte)'c' };
22+
protectedoverrideboolIsConstantByteOrCharArrayVariableDeclaratorSyntax(SemanticModelsemanticModel,SyntaxNodesyntax,outintlength)
23+
{
24+
length=0;
25+
26+
return
27+
syntaxisVariableDeclaratorSyntaxvariableDeclarator&&
28+
variableDeclarator.Initializer?.Valueis{}initializer&&
29+
IsConstantByteOrCharArrayCreationExpression(semanticModel,initializer,values:null,outlength);
30+
}
31+
32+
// ReadOnlySpan<char> myProperty => new char[] { 'a', 'b', 'c' };
33+
// ReadOnlySpan<char> myProperty => new[] { 'a', 'b', 'c' };
34+
// ReadOnlySpan<char> myProperty => "abc".ToCharArray();
35+
// ReadOnlySpan<char> myProperty => ConstString.ToCharArray();
36+
// ReadOnlySpan<byte> myProperty => new[] { (byte)'a', (byte)'b', (byte)'c' };
37+
// ReadOnlySpan<byte> myProperty => "abc"u8;
38+
// ReadOnlySpan<byte> myProperty { get => "abc"u8; }
39+
// ReadOnlySpan<byte> myProperty { get { return "abc"u8; } }
40+
protectedoverrideboolIsConstantByteOrCharReadOnlySpanPropertyDeclarationSyntax(SemanticModelsemanticModel,SyntaxNodesyntax,outintlength)
41+
{
42+
length=0;
43+
44+
return
45+
syntaxisPropertyDeclarationSyntaxpropertyDeclaration&&
46+
TryGetPropertyGetterExpression(propertyDeclaration)is{}expression&&
47+
(IsConstantByteOrCharArrayCreationExpression(semanticModel,expression,values:null,outlength)||IsUtf8StringLiteralExpression(expression,outlength));
48+
}
49+
50+
protectedoverrideboolIsConstantByteOrCharArrayCreationSyntax(SemanticModelsemanticModel,SyntaxNodesyntax,outintlength)
51+
{
52+
length=0;
53+
54+
return
55+
syntaxisExpressionSyntaxexpression&&
56+
IsConstantByteOrCharArrayCreationExpression(semanticModel,expression,values:null,outlength);
57+
}
58+
59+
internalstaticExpressionSyntax?TryGetPropertyGetterExpression(PropertyDeclarationSyntaxpropertyDeclaration)
60+
{
61+
varexpression=propertyDeclaration.ExpressionBody?.Expression;
62+
63+
if(expressionisnull&&
64+
propertyDeclaration.AccessorList?.Accessorsis[varaccessor]&&
65+
accessor.IsKind(SyntaxKind.GetAccessorDeclaration))
66+
{
67+
expression=accessor.ExpressionBody?.Expression;
68+
69+
if(expressionisnull&&
70+
accessor.Body?.Statementsis[varstatement]&&
71+
statementisReturnStatementSyntaxreturnStatement)
72+
{
73+
expression=returnStatement.Expression;
74+
}
75+
}
76+
77+
returnexpression;
78+
}
79+
80+
// new char[] { 'a', 'b', 'c' };
81+
// new[] { 'a', 'b', 'c' };
82+
// new[] { (byte)'a', (byte)'b', (byte)'c' };
83+
// "abc".ToCharArray()
84+
// ConstString.ToCharArray()
85+
internalstaticboolIsConstantByteOrCharArrayCreationExpression(SemanticModelsemanticModel,ExpressionSyntaxexpression,List<char>?values,outintlength)
86+
{
87+
length=0;
88+
89+
InitializerExpressionSyntax?arrayInitializer=null;
90+
91+
if(expressionisArrayCreationExpressionSyntaxarrayCreation)
92+
{
93+
arrayInitializer=arrayCreation.Initializer;
94+
}
95+
elseif(expressionisImplicitArrayCreationExpressionSyntaximplicitArrayCreation)
96+
{
97+
arrayInitializer=implicitArrayCreation.Initializer;
98+
}
99+
elseif(expressionisInvocationExpressionSyntaxinvocation)
100+
{
101+
if(semanticModel.GetOperation(invocation)isIInvocationOperationinvocationOperation&&
102+
IsConstantStringToCharArrayInvocation(invocationOperation,outstring?value))
103+
{
104+
values?.AddRange(value);
105+
length=value.Length;
106+
returntrue;
107+
}
108+
}
109+
110+
if(arrayInitializer?.Expressionsis{}valueExpressions)
111+
{
112+
foreach(varvalueExpressioninvalueExpressions)
113+
{
114+
if(!TryGetByteOrCharLiteral(valueExpression,outcharvalue))
115+
{
116+
returnfalse;
117+
}
118+
119+
values?.Add(value);
120+
}
121+
122+
length=valueExpressions.Count;
123+
returntrue;
124+
}
125+
126+
returnfalse;
127+
128+
// 'a' or (byte)'a'
129+
staticboolTryGetByteOrCharLiteral(ExpressionSyntax?expression,outcharvalue)
130+
{
131+
if(expressionis notnull)
132+
{
133+
if(expressionisCastExpressionSyntaxcast&&
134+
cast.TypeisPredefinedTypeSyntaxpredefinedType&&
135+
predefinedType.Keyword.IsKind(SyntaxKind.ByteKeyword))
136+
{
137+
expression=cast.Expression;
138+
}
139+
140+
if(expression.IsKind(SyntaxKind.CharacterLiteralExpression)&&
141+
expressionisLiteralExpressionSyntaxcharacterLiteral&&
142+
characterLiteral.Token.ValueischarcharValue)
143+
{
144+
value=charValue;
145+
returntrue;
146+
}
147+
}
148+
149+
value=default;
150+
returnfalse;
151+
}
152+
}
153+
154+
privatestaticboolIsUtf8StringLiteralExpression(ExpressionSyntaxexpression,outintlength)
155+
{
156+
constSyntaxKindUtf8StringLiteralExpression=(SyntaxKind)8756;
157+
constSyntaxKindUtf8StringLiteralToken=(SyntaxKind)8520;
158+
159+
if(expression.IsKind(Utf8StringLiteralExpression)&&
160+
expressionisLiteralExpressionSyntaxliteral&&
161+
literal.Token.IsKind(Utf8StringLiteralToken)&&
162+
literal.Token.Valueisstringvalue)
163+
{
164+
length=value.Length;
165+
returntrue;
166+
}
167+
168+
length=0;
169+
returnfalse;
170+
}
171+
172+
protectedoverrideboolArrayFieldUsesAreLikelyReadOnly(SyntaxNodesyntax)
173+
{
174+
if(syntaxis notVariableDeclaratorSyntaxvariableDeclarator||
175+
variableDeclarator.Identifier.Valueis notstringfieldName||
176+
syntax.FirstAncestorOrSelf<TypeDeclarationSyntax>()is not{}typeDeclaration)
177+
{
178+
returnfalse;
179+
}
180+
181+
// An optimistic implementation that only looks for simple assignments to the field or its array elements.
182+
foreach(varmemberintypeDeclaration.Members)
183+
{
184+
boolisCtor=member.IsKind(SyntaxKind.ConstructorDeclaration);
185+
186+
foreach(varnodeinmember.DescendantNodes())
187+
{
188+
if(node.IsKind(SyntaxKind.SimpleAssignmentExpression)&&
189+
nodeisAssignmentExpressionSyntaxassignment)
190+
{
191+
if(assignment.Left.IsKind(SyntaxKind.ElementAccessExpression))
192+
{
193+
if(assignment.LeftisElementAccessExpressionSyntaxelementAccess&&
194+
IsFieldReference(elementAccess.Expression,fieldName))
195+
{
196+
// s_array[42] = foo;
197+
returnfalse;
198+
}
199+
}
200+
elseif(isCtor)
201+
{
202+
if(IsFieldReference(assignment.Left,fieldName))
203+
{
204+
// s_array = foo;
205+
returnfalse;
206+
}
207+
}
208+
}
209+
}
210+
}
211+
212+
returntrue;
213+
214+
staticboolIsFieldReference(ExpressionSyntaxexpression,stringfieldName)=>
215+
expression.IsKind(SyntaxKind.IdentifierName)&&
216+
expressionisIdentifierNameSyntaxidentifierName&&
217+
identifierName.Identifier.Valueisstringvalue&&
218+
value==fieldName;
219+
}
220+
}
221+
}

‎src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ CA1861 | Performance | Info | AvoidConstArrays, [Documentation](https://learn.mi
2323
CA1862 | Performance | Info | RecommendCaseInsensitiveStringComparison,[Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862)
2424
CA1863 | Performance | Hidden | UseCompositeFormatAnalyzer,[Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862)
2525
CA1864 | Performance | Info | PreferDictionaryTryAddValueOverGuardedAddAnalyzer,[Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1864)
26+
CA1870 | Performance | Info | UseSearchValuesAnalyzer,[Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870)
2627
CA2021 | Reliability | Warning | DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer,[Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021)
2728

2829
###Removed Rules

‎src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,18 @@ Widening and user defined conversions are not supported with generic types.</val
19161916
<dataname="UseSpanClearInsteadOfFillTitle"xml:space="preserve">
19171917
<value>Prefer 'Clear' over 'Fill'</value>
19181918
</data>
1919+
<dataname="UseSearchValuesTitle"xml:space="preserve">
1920+
<value>Use a cached 'SearchValues' instance</value>
1921+
</data>
1922+
<dataname="UseSearchValuesMessage"xml:space="preserve">
1923+
<value>Use a cached 'SearchValues' instance for improved searching performance</value>
1924+
</data>
1925+
<dataname="UseSearchValuesDescription"xml:space="preserve">
1926+
<value>Using a cached 'SearchValues' instance is more efficient than passing values to 'IndexOfAny'/'ContainsAny' directly.</value>
1927+
</data>
1928+
<dataname="UseSearchValuesCodeFixTitle"xml:space="preserve">
1929+
<value>Use 'SearchValues'</value>
1930+
</data>
19191931
<dataname="PreventNumericIntPtrUIntPtrBehavioralChangesDescription"xml:space="preserve">
19201932
<value>Some built-in operators added in .NET 7 behave differently when overflowing than did the corresponding user-defined operators in .NET 6 and earlier versions. Some operators that previously threw in an unchecked context now don't throw unless wrapped within a checked context. Also, some operators that did not previously throw in a checked context now throw unless wrapped in an unchecked context.</value>
19211933
</data>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp