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

Add analyzer/fixer to suggest using cached SearchValues instances#6898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
buyaa-n merged 27 commits intodotnet:mainfromMihaZupan:searchvalues
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from16 commits
Commits
Show all changes
27 commits
Select commitHold shift + click to select a range
4f18da4
Initial Analyzer implementation
MihaZupanAug 24, 2023
69f39fa
Code fixer
MihaZupanAug 28, 2023
da368f2
Add support for string.IndexOfAny(char[])
MihaZupanAug 28, 2023
621aa7c
Catch simple cases of array element modification
MihaZupanAug 28, 2023
0dbfaf9
Use built-in helper instead of Linq
MihaZupanAug 28, 2023
942d49e
Also detect field assignments in ctor
MihaZupanAug 28, 2023
9a389dd
Move system namespace import to helper method
MihaZupanAug 28, 2023
e1bd88d
Replace array creations wtih string literals
MihaZupanAug 28, 2023
df31739
Add support for more property getter patterns
MihaZupanAug 29, 2023
b480ad5
Simplify test helper
MihaZupanAug 29, 2023
0c40d49
Revert Utf8String support :(
MihaZupanAug 29, 2023
3db5ce9
Update tests
MihaZupanAug 29, 2023
4fbc7cc
msbuild /t:pack /v:m
MihaZupanAug 29, 2023
2eafccc
Fix editor renaming
MihaZupanAug 29, 2023
a30f3e1
Exclude string uses on a conditional access
MihaZupanAug 29, 2023
153c682
Add test for array field with const char reference
MihaZupanAug 29, 2023
c1e6dff
Add back Utf8String support
MihaZupanAug 30, 2023
79c2c8b
Update messages/descriptions
MihaZupanAug 30, 2023
37a6eab
Add support for field initialized from literal.ToCharArray
MihaZupanAug 30, 2023
21578a8
More tests for ToCharArray
MihaZupanAug 30, 2023
32fe939
Better handle member names that start with _
MihaZupanAug 30, 2023
c8021ec
Avoid some duplication between Syntax and Operation analysis
MihaZupanSep 1, 2023
38ffb6d
Fix top-level statements and add logic to remove unused members
MihaZupanSep 2, 2023
644dd74
ImmutableHashSet, no OfType
MihaZupanSep 7, 2023
ac26e6b
Remove some duplication
MihaZupanSep 7, 2023
f6c1fab
Turn one analyzer test into code fixer tests
MihaZupanSep 7, 2023
1fe8dd8
Shorten analyzer title
MihaZupanSep 7, 2023
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
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.NetCore.Analyzers.Performance;

namespace Microsoft.NetCore.CSharp.Analyzers.Performance
{
/// <inheritdoc/>
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class CSharpUseSearchValuesFixer : UseSearchValuesFixer
{
protected override async ValueTask<(SyntaxNode TypeDeclaration, INamedTypeSymbol? TypeSymbol)> GetTypeSymbolAsync(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
{
SyntaxNode? typeDeclarationOrCompilationUnit = node.FirstAncestorOrSelf<TypeDeclarationSyntax>();

typeDeclarationOrCompilationUnit ??= await node.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

var typeSymbol = typeDeclarationOrCompilationUnit is TypeDeclarationSyntax typeDeclaration ?
semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken) :
semanticModel.GetDeclaredSymbol((CompilationUnitSyntax)typeDeclarationOrCompilationUnit, cancellationToken)?.ContainingType;

return (typeDeclarationOrCompilationUnit, typeSymbol);
}

protected override SyntaxNode ReplaceSearchValuesFieldName(SyntaxNode node)
{
if (node is FieldDeclarationSyntax fieldDeclaration &&
fieldDeclaration.Declaration is { } declaration &&
declaration.Variables is [var declarator])
{
var newDeclarator = declarator.ReplaceToken(declarator.Identifier, declarator.Identifier.WithAdditionalAnnotations(RenameAnnotation.Create()));
return fieldDeclaration.WithDeclaration(declaration.WithVariables(new SeparatedSyntaxList<VariableDeclaratorSyntax>().Add(newDeclarator)));
}

return node;
}

protected override SyntaxNode GetDeclaratorInitializer(SyntaxNode syntax)
{
if (syntax is VariableDeclaratorSyntax variableDeclarator)
{
return variableDeclarator.Initializer!.Value;
}

if (syntax is PropertyDeclarationSyntax propertyDeclaration)
{
return CSharpUseSearchValuesAnalyzer.TryGetPropertyGetterExpression(propertyDeclaration)!;
}

throw new InvalidOperationException($"Expected 'VariableDeclaratorSyntax' or 'PropertyDeclarationSyntax', got {syntax.GetType().Name}");
}

// new[] { 'a', 'b', 'c' } => "abc"
protected override SyntaxNode? TryReplaceArrayCreationWithInlineLiteralExpression(IOperation operation)
{
if (operation is IConversionOperation conversion)
{
operation = conversion.Operand;
}

if (operation is IArrayCreationOperation arrayCreation &&
arrayCreation.GetElementType() is { } elementType)
{
bool isByte = elementType.SpecialType == SpecialType.System_Byte;

if (isByte)
{
// Can't use Utf8StringLiterals
return null;
}

List<char> values = new();

if (arrayCreation.Syntax is ExpressionSyntax creationSyntax &&
CSharpUseSearchValuesAnalyzer.IsConstantByteOrCharArrayCreationExpression(creationSyntax, values, out _) &&
values.Count <= 128 && // Arbitrary limit to avoid emitting huge literals
!ContainsAnyComments(creationSyntax)) // Avoid removing potentially valuable comments
{
string valuesString = string.Concat(values);
string stringLiteral = SymbolDisplay.FormatLiteral(valuesString, quote: true);

return SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Token(
leading: default,
kind: SyntaxKind.StringLiteralToken,
text: stringLiteral,
valueText: valuesString,
trailing: default));
}
}

return null;
}

private static bool ContainsAnyComments(SyntaxNode node)
{
foreach (SyntaxTrivia trivia in node.DescendantTrivia(node.Span))
{
if (trivia.Kind() is SyntaxKind.SingleLineCommentTrivia or SyntaxKind.MultiLineCommentTrivia)
{
return true;
}
}

return false;
}
}
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.NetCore.Analyzers.Performance;

namespace Microsoft.NetCore.CSharp.Analyzers.Performance
{
/// <inheritdoc/>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpUseSearchValuesAnalyzer : UseSearchValuesAnalyzer
{
// char[] myField = new char[] { 'a', 'b', 'c' };
// char[] myField = new[] { 'a', 'b', 'c' };
// byte[] myField = new[] { (byte)'a', (byte)'b', (byte)'c' };
protected override bool IsConstantByteOrCharArrayVariableDeclaratorSyntax(SyntaxNode syntax, out int length)
{
length = 0;

return
syntax is VariableDeclaratorSyntax variableDeclarator &&
variableDeclarator.Initializer?.Value is { } initializer &&
IsConstantByteOrCharArrayCreationExpression(initializer, values: null, out length);
}

// ReadOnlySpan<char> myProperty => new char[] { 'a', 'b', 'c' };
// ReadOnlySpan<char> myProperty => new[] { 'a', 'b', 'c' };
// ReadOnlySpan<byte> myProperty => new[] { (byte)'a', (byte)'b', (byte)'c' };
// ReadOnlySpan<char> myProperty { get => new[] { 'a', 'b', 'c' }; }
// ReadOnlySpan<char> myProperty { get { return new[] { 'a', 'b', 'c' }; } }
protected override bool IsConstantByteOrCharReadOnlySpanPropertyDeclarationSyntax(SyntaxNode syntax, out int length)
{
if (syntax is PropertyDeclarationSyntax propertyDeclaration &&
TryGetPropertyGetterExpression(propertyDeclaration) is { } expression &&
IsConstantByteOrCharArrayCreationExpression(expression, values: null, out length))
{
return true;
}

length = 0;
return false;
}

internal static ExpressionSyntax? TryGetPropertyGetterExpression(PropertyDeclarationSyntax propertyDeclaration)
{
var expression = propertyDeclaration.ExpressionBody?.Expression;

if (expression is null &&
propertyDeclaration.AccessorList?.Accessors is [var accessor] &&
accessor.IsKind(SyntaxKind.GetAccessorDeclaration))
{
expression = accessor.ExpressionBody?.Expression;

if (expression is null &&
accessor.Body?.Statements is [var statement] &&
statement is ReturnStatementSyntax returnStatement)
{
expression = returnStatement.Expression;
}
}

return expression;
}

// new char[] { 'a', 'b', 'c' };
// new[] { 'a', 'b', 'c' };
// new[] { (byte)'a', (byte)'b', (byte)'c' };
internal static bool IsConstantByteOrCharArrayCreationExpression(ExpressionSyntax expression, List<char>? values, out int length)
{
length = 0;

InitializerExpressionSyntax? arrayInitializer = null;

if (expression is ArrayCreationExpressionSyntax arrayCreation)
{
arrayInitializer = arrayCreation.Initializer;
}
else if (expression is ImplicitArrayCreationExpressionSyntax implicitArrayCreation)
{
arrayInitializer = implicitArrayCreation.Initializer;
}

if (arrayInitializer?.Expressions is { } valueExpressions)
{
foreach (var valueExpression in valueExpressions)
{
if (!TryGetByteOrCharLiteral(valueExpression, out char value))
{
return false;
}

values?.Add(value);
}

length = valueExpressions.Count;
return true;
}

return false;

// 'a' or (byte)'a'
static bool TryGetByteOrCharLiteral(ExpressionSyntax? expression, out char value)
{
if (expression is not null)
{
if (expression is CastExpressionSyntax cast &&
cast.Type is PredefinedTypeSyntax predefinedType &&
predefinedType.Keyword.IsKind(SyntaxKind.ByteKeyword))
{
expression = cast.Expression;
}

if (expression.IsKind(SyntaxKind.CharacterLiteralExpression) &&
expression is LiteralExpressionSyntax characterLiteral &&
characterLiteral.Token.Value is char charValue)
{
value = charValue;
return true;
}
}

value = default;
return false;
}
}

protected override bool ArrayFieldUsesAreLikelyReadOnly(SyntaxNode syntax)
{
if (syntax is not VariableDeclaratorSyntax variableDeclarator ||
variableDeclarator.Identifier.Value is not string fieldName ||
syntax.FirstAncestorOrSelf<TypeDeclarationSyntax>() is not { } typeDeclaration)
{
return false;
}

// An optimistic implementation that only looks for simple assignments to the field or its array elements.
foreach (var member in typeDeclaration.Members)
{
bool isCtor = member.IsKind(SyntaxKind.ConstructorDeclaration);

foreach (var node in member.DescendantNodes())
{
if (node.IsKind(SyntaxKind.SimpleAssignmentExpression) &&
node is AssignmentExpressionSyntax assignment)
{
if (assignment.Left.IsKind(SyntaxKind.ElementAccessExpression))
{
if (assignment.Left is ElementAccessExpressionSyntax elementAccess &&
IsFieldReference(elementAccess.Expression, fieldName))
{
// s_array[42] = foo;
return false;
}
}
else if (isCtor)
{
if (IsFieldReference(assignment.Left, fieldName))
{
// s_array = foo;
return false;
}
}
}
}
}

return true;

static bool IsFieldReference(ExpressionSyntax expression, string fieldName) =>
expression.IsKind(SyntaxKind.IdentifierName) &&
expression is IdentifierNameSyntax identifierName &&
identifierName.Identifier.Value is string value &&
value == fieldName;
}
}
}
1 change: 1 addition & 0 deletionssrc/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,7 @@ CA1861 | Performance | Info | AvoidConstArrays, [Documentation](https://learn.mi
CA1862 | Performance | Info | RecommendCaseInsensitiveStringComparison, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862)
CA1863 | Performance | Hidden | UseCompositeFormatAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862)
CA1864 | Performance | Info | PreferDictionaryTryAddValueOverGuardedAddAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1864)
CA1870 | Performance | Info | UseSearchValuesAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870)
CA2021 | Reliability | Warning | DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021)

### Removed Rules
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -1982,6 +1982,12 @@ Widening and user defined conversions are not supported with generic types.</val
<data name="UseSpanClearInsteadOfFillTitle" xml:space="preserve">
<value>Prefer 'Clear' over 'Fill'</value>
</data>
<data name="UseSearchValuesTitle" xml:space="preserve">
<value>Use `SearchValues`</value>
</data>
<data name="UseSearchValuesDescription" xml:space="preserve">
<value>Use a cached `SearchValues` instance in `IndexOfAny`/`ContainsAny` calls for improved searching performance.</value>
</data>
<data name="PreventNumericIntPtrUIntPtrBehavioralChangesDescription" xml:space="preserve">
<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>
</data>
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp