From 05073cde89bdcf457346b0c516272b10b7df6275 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Thu, 3 Dec 2020 18:38:44 -0800 Subject: [PATCH 001/224] Update auto-generated documentation file --- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index c41ced8d15..37d29ac62f 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -5,7 +5,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.CSharp.NetAnalyzers", - "version": "5.0.1", + "version": "6.0.0", "language": "en-US" }, "rules": { @@ -290,7 +290,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.NetAnalyzers", - "version": "5.0.1", + "version": "6.0.0", "language": "en-US" }, "rules": { @@ -5068,7 +5068,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers", - "version": "5.0.1", + "version": "6.0.0", "language": "en-US" }, "rules": { From f83295ace79fb958991b77f83a8db642e4edc946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Tue, 8 Dec 2020 15:45:01 +0100 Subject: [PATCH 002/224] Remove rule CA1801 - replaced by IDE0060 --- .../CSharpReviewUnusedParameters.Fixer.cs | 28 - .../CSharpReviewUnusedParametersAnalyzer.cs | 35 - .../ReviewUnusedParameters.Fixer.cs | 186 --- .../Maintainability/ReviewUnusedParameters.cs | 252 --- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 - .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 40 - .../ReviewUnusedParametersTests.Fixer.cs | 896 ----------- .../ReviewUnusedParametersTests.cs | 1422 ----------------- .../BasicReviewUnusedParameters.Fixer.vb | 26 - .../BasicReviewUnusedParametersAnalyzer.vb | 20 - 10 files changed, 2917 deletions(-) delete mode 100644 src/NetAnalyzers/CSharp/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpReviewUnusedParameters.Fixer.cs delete mode 100644 src/NetAnalyzers/CSharp/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpReviewUnusedParametersAnalyzer.cs delete mode 100644 src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.Fixer.cs delete mode 100644 src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.cs delete mode 100644 src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParametersTests.Fixer.cs delete mode 100644 src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParametersTests.cs delete mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.CodeQuality.Analyzers/Maintainability/BasicReviewUnusedParameters.Fixer.vb delete mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.CodeQuality.Analyzers/Maintainability/BasicReviewUnusedParametersAnalyzer.vb diff --git a/src/NetAnalyzers/CSharp/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpReviewUnusedParameters.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpReviewUnusedParameters.Fixer.cs deleted file mode 100644 index ee4361aded..0000000000 --- a/src/NetAnalyzers/CSharp/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpReviewUnusedParameters.Fixer.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Composition; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeQuality.Analyzers.Maintainability; - -namespace Microsoft.CodeQuality.CSharp.Analyzers.Maintainability -{ - /// - /// CA1801: Review unused parameters - /// - [ExportCodeFixProvider(LanguageNames.CSharp), Shared] - public sealed class CSharpReviewUnusedParametersFixer : ReviewUnusedParametersFixer - { - protected override SyntaxNode GetParameterDeclarationNode(SyntaxNode node) - { - return node; - } - - protected override bool CanContinuouslyLeadToObjectCreationOrInvocation(SyntaxNode node) - { - var kind = node.Kind(); - return kind is SyntaxKind.QualifiedName or SyntaxKind.IdentifierName or SyntaxKind.SimpleMemberAccessExpression; - } - } -} \ No newline at end of file diff --git a/src/NetAnalyzers/CSharp/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpReviewUnusedParametersAnalyzer.cs b/src/NetAnalyzers/CSharp/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpReviewUnusedParametersAnalyzer.cs deleted file mode 100644 index 0880869ad8..0000000000 --- a/src/NetAnalyzers/CSharp/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpReviewUnusedParametersAnalyzer.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeQuality.Analyzers.Maintainability; - -namespace Microsoft.CodeQuality.CSharp.Analyzers.Maintainability -{ - /// - /// CA1801: Review unused parameters - /// - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class CSharpReviewUnusedParametersAnalyzer : ReviewUnusedParametersAnalyzer - { - private const SyntaxKind RecordDeclaration = (SyntaxKind)9063; - - protected override bool IsPositionalRecordPrimaryConstructor(IMethodSymbol methodSymbol) - { - if (methodSymbol.MethodKind != MethodKind.Constructor) - { - return false; - } - - if (methodSymbol.DeclaringSyntaxReferences.Length == 0) - { - return false; - } - - return methodSymbol.DeclaringSyntaxReferences[0] - .GetSyntax() - .IsKind(RecordDeclaration); - } - } -} diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.Fixer.cs deleted file mode 100644 index c7b31524fc..0000000000 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.Fixer.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Analyzer.Utilities; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Operations; - -namespace Microsoft.CodeQuality.Analyzers.Maintainability -{ - /// - /// CA1801: Review unused parameters - /// - public abstract class ReviewUnusedParametersFixer : CodeFixProvider - { - public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ReviewUnusedParametersAnalyzer.RuleId); - - public sealed override FixAllProvider GetFixAllProvider() - { - // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers - return WellKnownFixAllProviders.BatchFixer; - } - - public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) - { - foreach (var diagnostic in context.Diagnostics) - { - context.RegisterCodeFix( - new MyCodeAction( - MicrosoftCodeQualityAnalyzersResources.RemoveUnusedParameterMessage, - async ct => await RemoveNodes(context.Document, diagnostic, ct).ConfigureAwait(false), - equivalenceKey: MicrosoftCodeQualityAnalyzersResources.RemoveUnusedParameterMessage), - diagnostic); - } - - return Task.CompletedTask; - } - - /// - /// Gets parameter declaration node for a given diagnostic node. - /// Requires language specific implementation because - /// diagnostics are reported on different syntax nodes for across languages. - /// - /// the diagnostic node - /// the parameter declaration node - protected abstract SyntaxNode GetParameterDeclarationNode(SyntaxNode node); - - /// - /// Checks if the node has a proper kind for a subnode for a object creation or an invocation node. - /// Requires language specific implementation because node kinds checked are language specific. - /// - protected abstract bool CanContinuouslyLeadToObjectCreationOrInvocation(SyntaxNode node); - - private async Task RemoveNodes(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) - { - var solution = document.Project.Solution; - var pairs = await GetNodesToRemoveAsync(document, diagnostic, cancellationToken).ConfigureAwait(false); - foreach (var group in pairs.GroupBy(p => p.Key)) - { - DocumentEditor editor = await DocumentEditor.CreateAsync(solution.GetDocument(group.Key), cancellationToken).ConfigureAwait(false); - // Start removing from bottom to top to keep spans of nodes that are removed later. - foreach (var value in group.OrderByDescending(v => v.Value.SpanStart)) - { - editor.RemoveNode(value.Value); - } - - solution = solution.WithDocumentSyntaxRoot(group.Key, editor.GetChangedRoot()); - } - - return solution; - } - - private ImmutableArray? GetOperationArguments(SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken) - { - // For a given reference symbol node, find an object creation parent or an invocation parent. Then, return arguments of the parent found. - // For 'c(param1, param2)', the input node is 'c'. Then, we climb up to 'c(param1, param2)', and return [param1, param2]. - // For 'new A.B(param1, param2)', the input node is 'B'. Then, we climb up to 'new A.B(param1, param2)', and return [param1, param2]. - // For 'A.B.C(param1, param2)', the input node is 'C'. Then, we climb up to 'A.B.C(param1, param2)', and return [param1, param2]. - - // Consider operations like A.B.C(0). We start from 'C' and need to get to '0'. - // To achieve this, it is necessary to climb up to 'A.B.C' - // and check that this is IObjectCreationOperation or IInvocationOperation. - // Intermediate calls of GetOperation on 'B.C' and 'A.B.C.' return null. - // GetOperation on 'A.B.C(0)' returns a non-null operation. - // After that, it is possible to check its arguments. - // Return null in any unexpected situation, e.g. inconsistent tree. - while (node != null) - { - if (!CanContinuouslyLeadToObjectCreationOrInvocation(node)) - { - return null; - } - - node = node.Parent; - var operation = semanticModel.GetOperation(node, cancellationToken); - var arguments = (operation as IObjectCreationOperation)?.Arguments ?? (operation as IInvocationOperation)?.Arguments; - - if (arguments.HasValue) - { - return arguments.Value; - } - } - - return null; - } - - private async Task>> GetNodesToRemoveAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) - { - SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - SyntaxNode diagnosticNode = root.FindNode(diagnostic.Location.SourceSpan); - SyntaxNode declarationNode = GetParameterDeclarationNode(diagnosticNode); - - DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - ISymbol parameterSymbol = editor.SemanticModel.GetDeclaredSymbol(declarationNode, cancellationToken); - ISymbol methodDeclarationSymbol = parameterSymbol.ContainingSymbol; - - if (!IsSafeMethodToRemoveParameter(methodDeclarationSymbol)) - { - // See https://github.com/dotnet/roslyn-analyzers/issues/1466 - return ImmutableArray>.Empty; - } - - var nodesToRemove = ImmutableArray.CreateBuilder>(); - nodesToRemove.Add(new KeyValuePair(document.Id, declarationNode)); - var referencedSymbols = await SymbolFinder.FindReferencesAsync(methodDeclarationSymbol, document.Project.Solution, cancellationToken).ConfigureAwait(false); - - foreach (var referencedSymbol in referencedSymbols) - { - if (referencedSymbol.Locations != null) - { - foreach (var referenceLocation in referencedSymbol.Locations) - { - Location location = referenceLocation.Location; - var referenceRoot = location.SourceTree.GetRoot(cancellationToken); - var referencedSymbolNode = referenceRoot.FindNode(location.SourceSpan); - DocumentEditor localEditor = await DocumentEditor.CreateAsync(referenceLocation.Document, cancellationToken).ConfigureAwait(false); - var arguments = GetOperationArguments(referencedSymbolNode, localEditor.SemanticModel, cancellationToken); - - if (arguments != null) - { - foreach (IArgumentOperation argument in arguments) - { - // The name comparison below looks fragile. However, symbol comparison does not work for Reduced Extension Methods. Need to consider more reliable options. - if (string.Equals(argument.Parameter.Name, parameterSymbol.Name, StringComparison.Ordinal) && argument.ArgumentKind == ArgumentKind.Explicit) - { - nodesToRemove.Add(new KeyValuePair(referenceLocation.Document.Id, referenceRoot.FindNode(argument.Syntax.GetLocation().SourceSpan))); - } - } - } - } - } - } - - return nodesToRemove.ToImmutable(); - } - - private static bool IsSafeMethodToRemoveParameter(ISymbol methodDeclarationSymbol) - { - switch (methodDeclarationSymbol.Kind) - { - // Should not fix removing unused property indexer. - case SymbolKind.Property: - return false; - case SymbolKind.Method: - var methodSymbol = (IMethodSymbol)methodDeclarationSymbol; - // Should not remove parameter for a conversion operator. - return methodSymbol.MethodKind != MethodKind.Conversion; - default: - return true; - } - } - - private sealed class MyCodeAction : SolutionChangeAction - { - public MyCodeAction(string title, Func> createChangedSolution, string equivalenceKey) - : base(title, createChangedSolution, equivalenceKey) { } - } - } -} diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.cs b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.cs deleted file mode 100644 index 3174fdf251..0000000000 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.cs +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Concurrent; -using System.Collections.Immutable; -using System.Linq; -using Analyzer.Utilities; -using Analyzer.Utilities.Extensions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Operations; - -namespace Microsoft.CodeQuality.Analyzers.Maintainability -{ - /// - /// CA1801: Review unused parameters - /// - public abstract class ReviewUnusedParametersAnalyzer : DiagnosticAnalyzer - { - internal const string RuleId = "CA1801"; - - private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(MicrosoftCodeQualityAnalyzersResources.ReviewUnusedParametersTitle), MicrosoftCodeQualityAnalyzersResources.ResourceManager, typeof(MicrosoftCodeQualityAnalyzersResources)); - - private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(MicrosoftCodeQualityAnalyzersResources.ReviewUnusedParametersMessage), MicrosoftCodeQualityAnalyzersResources.ResourceManager, typeof(MicrosoftCodeQualityAnalyzersResources)); - private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(MicrosoftCodeQualityAnalyzersResources.ReviewUnusedParametersDescription), MicrosoftCodeQualityAnalyzersResources.ResourceManager, typeof(MicrosoftCodeQualityAnalyzersResources)); - - internal static DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(RuleId, - s_localizableTitle, - s_localizableMessage, - DiagnosticCategory.Usage, - RuleLevel.Disabled, // We have an implementation in IDE. - description: s_localizableDescription, - isPortedFxCopRule: true, - isDataflowRule: false); - - public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - - public sealed override void Initialize(AnalysisContext context) - { - context.EnableConcurrentExecution(); - - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - - context.RegisterCompilationStartAction(compilationStartContext => - { - var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(compilationStartContext.Compilation); - - INamedTypeSymbol? eventsArgSymbol = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemEventArgs); - - // Ignore conditional methods (FxCop compat - One conditional will often call another conditional method as its only use of a parameter) - INamedTypeSymbol? conditionalAttributeSymbol = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDiagnosticsConditionalAttribute); - - // Ignore methods with special serialization attributes (FxCop compat - All serialization methods need to take 'StreamingContext') - INamedTypeSymbol? onDeserializingAttribute = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeSerializationOnDeserializingAttribute); - INamedTypeSymbol? onDeserializedAttribute = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeSerializationOnDeserializedAttribute); - INamedTypeSymbol? onSerializingAttribute = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeSerializationOnSerializingAttribute); - INamedTypeSymbol? onSerializedAttribute = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeSerializationOnSerializedAttribute); - INamedTypeSymbol? obsoleteAttribute = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemObsoleteAttribute); - - INamedTypeSymbol? serializationInfoType = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeSerializationSerializationInfo); - INamedTypeSymbol? streamingContextType = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeSerializationStreamingContext); - - ImmutableHashSet attributeSetForMethodsToIgnore = ImmutableHashSet.Create( - conditionalAttributeSymbol, - onDeserializedAttribute, - onDeserializingAttribute, - onSerializedAttribute, - onSerializingAttribute, - obsoleteAttribute); - - compilationStartContext.RegisterSymbolStartAction(symbolStartContext => - { - // Map from parameter to a bool indicating if the parameter is used or not. - var parameterUsageMap = new ConcurrentDictionary(); - - // Set of methods which are used as delegates. - var methodsUsedAsDelegates = new ConcurrentDictionary(); - - // Add candidate parameters for methods. - symbolStartContext.RegisterOperationBlockStartAction(startOperationBlockContext => - { - if (startOperationBlockContext.OwningSymbol is IMethodSymbol method && - ShouldAnalyzeMethod(method, startOperationBlockContext, eventsArgSymbol, attributeSetForMethodsToIgnore, serializationInfoType, streamingContextType)) - { - AddParameters(method, parameterUsageMap); - } - }); - - // Add candidate parameters for local functions. - symbolStartContext.RegisterOperationAction( - context => AddParameters(((ILocalFunctionOperation)context.Operation).Symbol, parameterUsageMap), - OperationKind.LocalFunction); - - // Add methods used as delegates. - symbolStartContext.RegisterOperationAction( - context => methodsUsedAsDelegates.TryAdd(((IMethodReferenceOperation)context.Operation).Method.OriginalDefinition, true), - OperationKind.MethodReference); - - // Mark parameters with a parameter reference as used. - symbolStartContext.RegisterOperationAction( - context => parameterUsageMap.AddOrUpdate( - ((IParameterReferenceOperation)context.Operation).Parameter, - addValue: true, - updateValueFactory: ReturnTrue), - OperationKind.ParameterReference); - - // Report unused parameters in SymbolEnd action. - symbolStartContext.RegisterSymbolEndAction( - context => ReportUnusedParameters(context, parameterUsageMap, methodsUsedAsDelegates)); - }, SymbolKind.NamedType); - }); - } - - private static bool ReturnTrue(IParameterSymbol param, bool value) => true; - - private static void AddParameters(IMethodSymbol method, ConcurrentDictionary unusedParameters) - { - foreach (var parameter in method.Parameters) - { - unusedParameters.TryAdd(parameter, false); - } - } - - private static void ReportUnusedParameters( - SymbolAnalysisContext symbolEndContext, - ConcurrentDictionary parameterUsageMap, - ConcurrentDictionary methodsUsedAsDelegates) - { - // Report diagnostics for unused parameters. - foreach (var (parameter, used) in parameterUsageMap) - { - if (used || parameter.Name.Length == 0) - { - continue; - } - - var containingMethod = (IMethodSymbol)parameter.ContainingSymbol; - - // Don't report parameters for methods used as delegates. - // We assume these methods have signature requirements, - // and hence its unused parameters cannot be removed. - if (methodsUsedAsDelegates.ContainsKey(containingMethod)) - { - continue; - } - - // Do not flag unused 'this' parameter of an extension method. - if (containingMethod.IsExtensionMethod && parameter.Ordinal == 0) - { - continue; - } - - // Do not flag unused parameters with special discard symbol name. - if (parameter.IsSymbolWithSpecialDiscardName()) - { - continue; - } - - var diagnostic = parameter.CreateDiagnostic(Rule, parameter.Name, parameter.ContainingSymbol.Name); - symbolEndContext.ReportDiagnostic(diagnostic); - } - } - -#pragma warning disable RS1012 // Start action has no registered actions. - private bool ShouldAnalyzeMethod( - IMethodSymbol method, - OperationBlockStartAnalysisContext startOperationBlockContext, - INamedTypeSymbol? eventsArgSymbol, - ImmutableHashSet attributeSetForMethodsToIgnore, - INamedTypeSymbol? serializationInfoType, - INamedTypeSymbol? streamingContextType) -#pragma warning restore RS1012 // Start action has no registered actions. - { - // We only care about methods with parameters. - if (method.Parameters.IsEmpty) - { - return false; - } - - // Ignore implicitly declared methods, extern methods, abstract methods, virtual methods, interface implementations and finalizers (FxCop compat). - if (method.IsImplicitlyDeclared || - method.IsExtern || - method.IsAbstract || - method.IsVirtual || - method.IsOverride || - method.IsImplementationOfAnyInterfaceMember() || - method.IsFinalizer()) - { - return false; - } - - // Ignore property accessors. - if (method.IsPropertyAccessor()) - { - return false; - } - - // Ignore primary constructor (body-less) of positional records. - if (IsPositionalRecordPrimaryConstructor(method)) - { - return false; - } - - // Ignore serialization special methods - if (method.IsSerializationConstructor(serializationInfoType, streamingContextType) || - method.IsGetObjectData(serializationInfoType, streamingContextType)) - { - return false; - } - - // Ignore event handler methods "Handler(object, MyEventArgs)" - if (method.HasEventHandlerSignature(eventsArgSymbol)) - { - return false; - } - - // Ignore methods with any attributes in 'attributeSetForMethodsToIgnore'. - if (method.GetAttributes().Any(a => a.AttributeClass != null && attributeSetForMethodsToIgnore.Contains(a.AttributeClass))) - { - return false; - } - - // Bail out if user has configured to skip analysis for the method. - if (!startOperationBlockContext.Options.MatchesConfiguredVisibility( - Rule, - method, - startOperationBlockContext.Compilation, - startOperationBlockContext.CancellationToken, - defaultRequiredVisibility: SymbolVisibilityGroup.All)) - { - return false; - } - - // Check to see if the method just throws a NotImplementedException/NotSupportedException - // We shouldn't warn about parameters in that case - if (startOperationBlockContext.IsMethodNotImplementedOrSupported()) - { - return false; - } - - // Ignore generated method for top level statements - if (method.IsTopLevelStatementsEntryPointMethod()) - { - return false; - } - - return true; - } - - protected abstract bool IsPositionalRecordPrimaryConstructor(IMethodSymbol methodSymbol); - } -} diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 414dec219d..1450202fc9 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -948,18 +948,6 @@ Consistent naming of parameters in an override hierarchy increases the usability |CodeFix|True| --- -## [CA1801](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801): Review unused parameters - -Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names. - -|Item|Value| -|-|-| -|Category|Usage| -|Enabled|False| -|Severity|Warning| -|CodeFix|True| ---- - ## [CA1802](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1802): Use literals where appropriate A field is declared static and read-only (Shared and ReadOnly in Visual Basic), and is initialized by using a value that is computable at compile time. Because the value that is assigned to the targeted field is computable at compile time, change the declaration to a const (Const in Visual Basic) field so that the value is computed at compile time instead of at run?time. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index b7047e3d42..4b6d6f840a 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -147,26 +147,6 @@ ] } }, - "CA1801": { - "id": "CA1801", - "shortDescription": "Review unused parameters", - "fullDescription": "Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names.", - "defaultLevel": "warning", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801", - "properties": { - "category": "Usage", - "isEnabledByDefault": false, - "typeName": "CSharpReviewUnusedParametersAnalyzer", - "languages": [ - "C#" - ], - "tags": [ - "PortedFromFxCop", - "Telemetry", - "EnabledRuleInAggressiveMode" - ] - } - }, "CA1812": { "id": "CA1812", "shortDescription": "Avoid uninstantiated internal classes", @@ -5209,26 +5189,6 @@ ] } }, - "CA1801": { - "id": "CA1801", - "shortDescription": "Review unused parameters", - "fullDescription": "Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names.", - "defaultLevel": "warning", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801", - "properties": { - "category": "Usage", - "isEnabledByDefault": false, - "typeName": "BasicReviewUnusedParametersAnalyzer", - "languages": [ - "Visual Basic" - ], - "tags": [ - "PortedFromFxCop", - "Telemetry", - "EnabledRuleInAggressiveMode" - ] - } - }, "CA1812": { "id": "CA1812", "shortDescription": "Avoid uninstantiated internal classes", diff --git a/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParametersTests.Fixer.cs b/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParametersTests.Fixer.cs deleted file mode 100644 index f1b2c1bb6e..0000000000 --- a/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParametersTests.Fixer.cs +++ /dev/null @@ -1,896 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Testing; -using Xunit; -using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.CodeQuality.CSharp.Analyzers.Maintainability.CSharpReviewUnusedParametersAnalyzer, - Microsoft.CodeQuality.CSharp.Analyzers.Maintainability.CSharpReviewUnusedParametersFixer>; -using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< - Microsoft.CodeQuality.VisualBasic.Analyzers.Maintainability.BasicReviewUnusedParametersAnalyzer, - Microsoft.CodeQuality.VisualBasic.Analyzers.Maintainability.BasicReviewUnusedParametersFixer>; - -namespace Microsoft.CodeQuality.Analyzers.Maintainability.UnitTests -{ - public class ReviewUnusedParametersFixerTests - { - [Fact] - public async Task BaseScenario_CSharp() - { - var code = @" -using System; - -class C -{ - public int Property1 { get; set; } - - public int Field1; - - public C(int [|param|]) - { - } - - public void UnusedParamMethod(int [|param|]) - { - } - - public static void UnusedParamStaticMethod(int [|param1|]) - { - } - - public void UnusedDefaultParamMethod(int [|defaultParam|] = 1) - { - } - - public void UnusedParamsArrayParamMethod(params int[] [|paramsArr|]) - { - } - - public void MultipleUnusedParamsMethod(int [|param1|], int [|param2|]) - { - } - - private void UnusedRefParamMethod(ref int [|param1|]) - { - } - - public void UnusedErrorTypeParamMethod({|CS0246:UndefinedType|} [|param1|]) // error CS0246: The type or namespace name 'UndefinedType' could not be found. - { - } - - public void Caller() - { - var c = new C(0); - UnusedParamMethod(this.Property1); - int b = 0; - UnusedParamMethod(b); - UnusedParamStaticMethod(1 + 1); - UnusedDefaultParamMethod(this.Field1); - UnusedParamsArrayParamMethod(new int[0]); - MultipleUnusedParamsMethod(0, 1); - int a = 0; - UnusedRefParamMethod(ref a); - } -} -"; - var fix = @" -using System; - -class C -{ - public int Property1 { get; set; } - - public int Field1; - - public C() - { - } - - public void UnusedParamMethod() - { - } - - public static void UnusedParamStaticMethod() - { - } - - public void UnusedDefaultParamMethod() - { - } - - public void UnusedParamsArrayParamMethod() - { - } - - public void MultipleUnusedParamsMethod() - { - } - - private void UnusedRefParamMethod() - { - } - - public void UnusedErrorTypeParamMethod() // error CS0246: The type or namespace name 'UndefinedType' could not be found. - { - } - - public void Caller() - { - var c = new C(); - UnusedParamMethod(); - int b = 0; - UnusedParamMethod(); - UnusedParamStaticMethod(); - UnusedDefaultParamMethod(); - UnusedParamsArrayParamMethod(); - MultipleUnusedParamsMethod(); - int a = 0; - UnusedRefParamMethod(); - } -} -"; - await new VerifyCS.Test - { - TestState = { Sources = { code } }, - FixedState = { Sources = { fix } }, - NumberOfFixAllIterations = 2, - }.RunAsync(); - } - - [Fact] - public async Task ExternalFileScenario_CSharp() - { - var code = @" -class C -{ - public static void UnusedParamStaticMethod(int [|param1|]) - { - } -} - -class D -{ - public void Caller() - { - C.UnusedParamStaticMethod(0); - E.M(0); - } -} -"; - var fix = @" -class C -{ - public static void UnusedParamStaticMethod() - { - } -} - -class D -{ - public void Caller() - { - C.UnusedParamStaticMethod(); - E.M(); - } -} -"; - - var anotherCode = @" -class E -{ - public static void M(int [|param1|]) { } -} -"; - var anotherCodeFix = @" -class E -{ - public static void M() { } -} -"; - await new VerifyCS.Test - { - TestState = - { - Sources = - { - code, - anotherCode, - }, - }, - FixedState = - { - Sources = - { - fix, - anotherCodeFix, - }, - }, - }.RunAsync(); - } - - [Fact] - public async Task CommentsNearParams_CSharp() - { - var code = @" -class C -{ - public C(/* comment left */ int /* comment middle */ [|param|] /* comment right */) - { - } - - public int M(/* comment 1 */ int /* comment 2 */ [|param1|] /* comment 3 */, /* comment 4 */ int /* comment 5 */ param2 /* comment 6 */) - { - return param2; - } - - public void Caller() - { - var c = new C(/* caller comment left */ 0 /* caller comment right */); - M(/* comment 1 */ 0 /* comment 2 */, /* comment 3 */ 1 /* comment 4 */); - } -} -"; - var fix = @" -class C -{ - public C(/* comment left */ ) - { - } - - public int M(/* comment 1 */ int /* comment 5 */ param2 /* comment 6 */) - { - return param2; - } - - public void Caller() - { - var c = new C(/* caller comment left */ ); - M(/* comment 1 */ 1 /* comment 4 */); - } -} -"; - await VerifyCS.VerifyCodeFixAsync(code, fix); - } - - [Fact] - public async Task NamedParams_CSharp() - { - var code = @" -class C -{ - public int UnusedParamMethod(int param1, int [|param2|]) - { - return param1; - } - - public void Caller() - { - UnusedParamMethod(param2: 0, param1: 1); - } -} -"; - var fix = @" -class C -{ - public int UnusedParamMethod(int param1) - { - return param1; - } - - public void Caller() - { - UnusedParamMethod(param1: 1); - } -} -"; - await VerifyCS.VerifyCodeFixAsync(code, fix); - } - - [Fact] - public async Task MultipleNamespaces_CSharp() - { - var code = @" -namespace A.B.C.D -{ - public class Test - { - public Test(int [|param1|]) { } - - public static void UnusedParamMethod(int [|param1|]) { } - } -} - -namespace E -{ - class CallerClass - { - public void Caller() - { - var test = new A.B.C.D.Test(0); - A.B.C.D.Test.UnusedParamMethod(0); - } - } -} -"; - var fix = @" -namespace A.B.C.D -{ - public class Test - { - public Test() { } - - public static void UnusedParamMethod() { } - } -} - -namespace E -{ - class CallerClass - { - public void Caller() - { - var test = new A.B.C.D.Test(); - A.B.C.D.Test.UnusedParamMethod(); - } - } -} -"; - await VerifyCS.VerifyCodeFixAsync(code, fix); - } - - [Fact] - public async Task CalculationsInParameter_CSharp() - { - var code = @" -class C -{ - void M() { } - - int N(int x) => x; - - void Caller() - { - M(); - } -} -"; - await VerifyCS.VerifyCodeFixAsync(code, code); - } - - [Fact] - public async Task Conversion_CSharp() - { - var code = @" -class C -{ - public static explicit operator int(C [|value|]) => 0; - - public void M1(double [|d|]) { } - - public void M2(int [|i|]) { } - - public void M3(int [|x|]) { } - - public void Caller() - { - int i = 0; - M1(i); - double d = 0; - M2((int)d); - var instance = new C(); - M3((int)instance); - } -} -"; - var fix = @" -class C -{ - public static explicit operator int(C [|value|]) => 0; - - public void M1() { } - - public void M2() { } - - public void M3() { } - - public void Caller() - { - int i = 0; - M1(); - double d = 0; - M2(); - var instance = new C(); - M3(); - } -} -"; - await new VerifyCS.Test - { - TestState = - { - Sources = { code }, - }, - FixedState = - { - Sources = { fix }, - MarkupHandling = MarkupMode.Allow, - }, - }.RunAsync(); - } - - [Fact] - public async Task ExtensionMethod_CSharp() - { - var code = @" -static class C -{ - static void ExtensionMethod(this int i) { } - static void ExtensionMethod(this int i, int [|anotherParam|]) { } - - static void Caller() - { - int i = 0; - i.ExtensionMethod(); - i.ExtensionMethod(i); - } -} -"; - var fix = @" -static class C -{ - static void ExtensionMethod(this int i) { } - static void {|CS0111:ExtensionMethod|}(this int i) { } - - static void Caller() - { - int i = 0; - i.{|CS0121:ExtensionMethod|}(); - i.{|CS0121:ExtensionMethod|}(); - } -} -"; - await VerifyCS.VerifyCodeFixAsync(code, fix); - } - - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/22449")] - public async Task DictionaryConstructor_CSharp() - { - var code = @" -using System.Collections.Generic; - -class Dict : Dictionary -{ - public void Add(int key, int a, int [|b|]) - { - var val = new MyValue(); - val.A = a; - this.Add(key, val); - } - - public static Dict Create() - { - return new Dict() - { - {0, 1, 2} - }; - } -} - -class MyValue -{ - public int A; -} -"; - - var fix = @" -using System.Collections.Generic; - -class Dict : Dictionary -{ - public void Add(int key, int a) - { - var val = new MyValue(); - val.A = a; - this.Add(key, val); - } - - public static Dict Create() - { - return new Dict() - { - {0, 1} - }; - } -} - -class MyValue -{ - public int A; -} -"; - await VerifyCS.VerifyCodeFixAsync(code, fix); - } - - [Fact] - public async Task BaseScenario_Basic() - { - var code = @" -Class C - Public Property Property1 As Integer - - Public Field1 As Integer - - Public Sub New([|param|] As Integer) - End Sub - - Public Sub UnusedParamMethod([|param|] As Integer) - End Sub - - Public Shared Sub UnusedParamStaticMethod([|param1|] As Integer) - End Sub - - Public Sub UnusedDefaultParamMethod(Optional [|defaultParam|] As Integer = 1) - End Sub - - Public Sub UnusedParamsArrayParamMethod(ParamArray [|paramsArr|] As Integer()) - End Sub - - Public Sub MultipleUnusedParamsMethod([|param1|] As Integer, [|param2|] As Integer) - End Sub - - Private Sub UnusedRefParamMethod(ByRef [|param1|] As Integer) - End Sub - - Public Sub UnusedErrorTypeParamMethod([|param1|] As {|BC30002:UndefinedType|}) ' error BC30002: Type 'UndefinedType' is not defined. - End Sub - - Public Sub Caller() - Dim c = New C(0) - UnusedParamMethod(Property1) - Dim b As Integer = 0 - UnusedParamMethod(b) - UnusedParamStaticMethod(1 + 1) - UnusedDefaultParamMethod(Field1) - UnusedParamsArrayParamMethod(New Integer() {}) - MultipleUnusedParamsMethod(0, 1) - Dim a As Integer = 0 - UnusedRefParamMethod(a) - End Sub -End Class -"; - var fix = @" -Class C - Public Property Property1 As Integer - - Public Field1 As Integer - - Public Sub New() - End Sub - - Public Sub UnusedParamMethod() - End Sub - - Public Shared Sub UnusedParamStaticMethod() - End Sub - - Public Sub UnusedDefaultParamMethod() - End Sub - - Public Sub UnusedParamsArrayParamMethod() - End Sub - - Public Sub MultipleUnusedParamsMethod() - End Sub - - Private Sub UnusedRefParamMethod() - End Sub - - Public Sub UnusedErrorTypeParamMethod() ' error BC30002: Type 'UndefinedType' is not defined. - End Sub - - Public Sub Caller() - Dim c = New C() - UnusedParamMethod() - Dim b As Integer = 0 - UnusedParamMethod() - UnusedParamStaticMethod() - UnusedDefaultParamMethod() - UnusedParamsArrayParamMethod() - MultipleUnusedParamsMethod() - Dim a As Integer = 0 - UnusedRefParamMethod() - End Sub -End Class -"; - await new VerifyVB.Test - { - TestState = { Sources = { code } }, - FixedState = { Sources = { fix } }, - NumberOfFixAllIterations = 2, - }.RunAsync(); - } - - [Fact] - public async Task ExternalFileScenario_Basic() - { - var code = @" -Class C - Public Shared Sub UnusedParamStaticMethod([|param1|] As Integer) - End Sub -End Class - -Class D - Public Sub Caller() - C.UnusedParamStaticMethod(0) - E.M(0) - End Sub -End Class -"; - var fix = @" -Class C - Public Shared Sub UnusedParamStaticMethod() - End Sub -End Class - -Class D - Public Sub Caller() - C.UnusedParamStaticMethod() - E.M() - End Sub -End Class -"; - - var anotherCode = @" -Class E - Public Shared Sub M([|param1|] As Integer) - End Sub -End Class -"; - var anotherCodeFix = @" -Class E - Public Shared Sub M() - End Sub -End Class -"; - await new VerifyVB.Test - { - TestState = - { - Sources = - { - code, - anotherCode, - }, - }, - FixedState = - { - Sources = - { - fix, - anotherCodeFix, - }, - }, - }.RunAsync(); - } - - [Fact] - public async Task NamedParams_Basic() - { - var code = @" -Class C - Public Function UnusedParamMethod(param1 As Integer, [|param2|] As Integer) As Integer - Return param1 - End Function - - Public Sub Caller() - UnusedParamMethod(param2:=0, param1:=1) - End Sub -End Class -"; - var fix = @" -Class C - Public Function UnusedParamMethod(param1 As Integer) As Integer - Return param1 - End Function - - Public Sub Caller() - UnusedParamMethod(param1:=1) - End Sub -End Class -"; - await VerifyVB.VerifyCodeFixAsync(code, fix); - } - - [Fact] - public async Task MultipleNamespaces_Basic() - { - var code = @" -Namespace A.B.C.D - Public Class Test - Public Sub New([|param1|] As Integer) - End Sub - - Public Shared Sub UnusedParamMethod([|param1|] As Integer) - End Sub - End Class -End Namespace - -Namespace E - Class CallerClass - Public Sub Caller() - Dim test = New A.B.C.D.Test(0) - A.B.C.D.Test.UnusedParamMethod(0) - End Sub - End Class -End Namespace -"; - var fix = @" -Namespace A.B.C.D - Public Class Test - Public Sub New() - End Sub - - Public Shared Sub UnusedParamMethod() - End Sub - End Class -End Namespace - -Namespace E - Class CallerClass - Public Sub Caller() - Dim test = New A.B.C.D.Test() - A.B.C.D.Test.UnusedParamMethod() - End Sub - End Class -End Namespace -"; - await VerifyVB.VerifyCodeFixAsync(code, fix); - } - - [Fact] - public async Task CalculationsInParameter_Basic() - { - var code = @" -Class C - Sub M([|x|] As Integer) - End Sub - - Function N(x As Integer) As Integer - Return x - End Function - - Sub Caller() - M(N(0)) - End Sub -End Class -"; - - var fix = @" -Class C - Sub M() - End Sub - - Function N(x As Integer) As Integer - Return x - End Function - - Sub Caller() - M() - End Sub -End Class -"; - await VerifyVB.VerifyCodeFixAsync(code, fix); - } - - [Fact] - public async Task Conversion_Basic() - { - var code = @" -Class C - Public Shared Narrowing Operator CType([|value|] As C) As Integer - Return 0 - End Operator - - Public Sub M1([|d|] As Double) - End Sub - - Public Sub M2([|i|] As Integer) - End Sub - - Public Sub M3([|x|] As Integer) - End Sub - - Public Sub Caller() - Dim i As Integer = 0 - M1(i) - Dim d As Double = 0 - M2(CInt(d)) - Dim instance = New C() - M3(CType(instance, Integer)) - End Sub -End Class -"; - var fix = @" -Class C - Public Shared Narrowing Operator CType([|value|] As C) As Integer - Return 0 - End Operator - - Public Sub M1() - End Sub - - Public Sub M2() - End Sub - - Public Sub M3() - End Sub - - Public Sub Caller() - Dim i As Integer = 0 - M1() - Dim d As Double = 0 - M2() - Dim instance = New C() - M3() - End Sub -End Class -"; - await new VerifyVB.Test - { - TestState = - { - Sources = { code }, - }, - FixedState = - { - Sources = { fix }, - MarkupHandling = MarkupMode.Allow, - }, - }.RunAsync(); - } - - [Fact] - public async Task ExtensionMethod_Basic() - { - var code = @" -Imports System.Runtime.CompilerServices - -Module D - - Public Sub ExtensionMethod(s As String) - End Sub - - - Public Sub ExtensionMethod(s As String, [|i|] As Integer) - End Sub - - Sub Caller() - Dim s as String - s.ExtensionMethod() - s.ExtensionMethod(0) - End Sub -End Module -"; - var fix = @" -Imports System.Runtime.CompilerServices - -Module D - - Public Sub {|BC30269:ExtensionMethod|}(s As String) - End Sub - - - Public Sub ExtensionMethod(s As String) - End Sub - - Sub Caller() - Dim s as String - s.{|BC30521:ExtensionMethod|}() - s.{|BC30521:ExtensionMethod|}() - End Sub -End Module -"; - await VerifyVB.VerifyCodeFixAsync(code, fix); - } - } -} \ No newline at end of file diff --git a/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParametersTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParametersTests.cs deleted file mode 100644 index 10ebc67610..0000000000 --- a/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParametersTests.cs +++ /dev/null @@ -1,1422 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Text; -using Test.Utilities; -using Xunit; -using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.CodeQuality.CSharp.Analyzers.Maintainability.CSharpReviewUnusedParametersAnalyzer, - Microsoft.CodeQuality.CSharp.Analyzers.Maintainability.CSharpReviewUnusedParametersFixer>; -using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< - Microsoft.CodeQuality.VisualBasic.Analyzers.Maintainability.BasicReviewUnusedParametersAnalyzer, - Microsoft.CodeQuality.VisualBasic.Analyzers.Maintainability.BasicReviewUnusedParametersFixer>; - -namespace Microsoft.CodeQuality.Analyzers.Maintainability.UnitTests -{ - public class ReviewUnusedParametersTests - { - #region Unit tests for no analyzer diagnostic - [Fact] - [WorkItem(4039, "https://github.com/dotnet/roslyn-analyzers/issues/4039")] - public async Task NoDiagnosticForUnnamedParameterTest() - { - await VerifyCS.VerifyAnalyzerAsync(@" -public class NeatCode -{ - public void DoSomething(string) - { - } -} -", DiagnosticResult.CompilerError("CS1001").WithLocation(4, 35)); - } - - [Fact] - [WorkItem(459, "https://github.com/dotnet/roslyn-analyzers/issues/459")] - public async Task NoDiagnosticSimpleCasesTest() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class NeatCode -{ - // Used parameter methods - public void UsedParameterMethod1(string use) - { - Console.WriteLine(this); - Console.WriteLine(use); - } - - public void UsedParameterMethod2(string use) - { - UsedParameterMethod3(ref use); - } - - public void UsedParameterMethod3(ref string use) - { - use = null; - } -} -"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System - -Public Class NeatCode - ' Used parameter methods - Public Sub UsedParameterMethod1(use As String) - Console.WriteLine(Me) - Console.WriteLine(use) - End Sub - - Public Sub UsedParameterMethod2(use As String) - UsedParameterMethod3(use) - End Sub - - Public Sub UsedParameterMethod3(ByRef use As String) - use = Nothing - End Sub -End Class -"); - } - - [Fact] - [WorkItem(459, "https://github.com/dotnet/roslyn-analyzers/issues/459")] - public async Task NoDiagnosticDelegateTest() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class NeatCode -{ - // Used parameter methods - public void UsedParameterMethod1(Action a) - { - a(); - } - - public void UsedParameterMethod2(Action a1, Action a2) - { - try - { - a1(); - } - catch(Exception) - { - a2(); - } - } -} -"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System - -Public Class NeatCode - ' Used parameter methods - Public Sub UsedParameterMethod1(a As Action) - a() - End Sub - - Public Sub UsedParameterMethod2(a1 As Action, a2 As Action) - Try - a1() - Catch generatedExceptionName As Exception - a2() - End Try - End Sub -End Class -"); - } - - [Fact] - [WorkItem(8884, "https://github.com/dotnet/roslyn/issues/8884")] - public async Task NoDiagnosticDelegateTest2_CSharp() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class NeatCode -{ - // Used parameter methods - public void UsedParameterMethod1(Action a) - { - Action a2 = new Action(() => - { - a(); - }); - } -}"); - } - - [Fact] - [WorkItem(459, "https://github.com/dotnet/roslyn-analyzers/issues/459")] - public async Task NoDiagnosticDelegateTest2_VB() - { - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System - -Public Class NeatCode - ' Used parameter methods - Public Sub UsedParameterMethod1(a As Action) - Dim a2 As New Action(Sub() - a() - End Sub) - End Sub -End Class -"); - } - - [Fact] - [WorkItem(8884, "https://github.com/dotnet/roslyn/issues/8884")] - public async Task NoDiagnosticUsingTest_CSharp() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -class C -{ - void F(int x, IDisposable o) - { - using (o) - { - int y = x; - } - } -} -"); - } - - [Fact] - [WorkItem(8884, "https://github.com/dotnet/roslyn/issues/8884")] - public async Task NoDiagnosticUsingTest_VB() - { - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System - -Class C - Private Sub F(x As Integer, o As IDisposable) - Using o - Dim y As Integer = x - End Using - End Sub -End Class -"); - } - - [Fact] - [WorkItem(8884, "https://github.com/dotnet/roslyn/issues/8884")] - public async Task NoDiagnosticLinqTest_CSharp() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; -using System.Linq; -using System.Reflection; - -class C -{ - private object F(Assembly assembly) - { - var type = (from t in assembly.GetTypes() - select t.Attributes).FirstOrDefault(); - return type; - } -} -"); - } - - [Fact] - [WorkItem(8884, "https://github.com/dotnet/roslyn/issues/8884")] - public async Task NoDiagnosticLinqTest_VB() - { - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System -Imports System.Linq -Imports System.Reflection - -Class C - Private Function F(assembly As Assembly) As Object - Dim type = (From t In assembly.DefinedTypes() Select t.Attributes).FirstOrDefault() - Return type - End Function -End Class -"); - } - - [Fact] - [WorkItem(459, "https://github.com/dotnet/roslyn-analyzers/issues/459")] - public async Task NoDiagnosticSpecialCasesTest() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; -using System.Runtime.InteropServices; - -public abstract class Derived : Base, I -{ - // Override - public override void VirtualMethod(int param) - { - } - - // Abstract - public abstract void AbstractMethod(int param); - - // Implicit interface implementation - public void Method1(int param) - { - } - - // Explicit interface implementation - void I.Method2(int param) - { - } - - // Event handlers - public void MyEventHandler(object o, EventArgs e) - { - } - - public void MyEventHandler2(object o, MyEventArgs e) - { - } - - public class MyEventArgs : EventArgs { } -} - -public class Base -{ - // Virtual - public virtual void VirtualMethod(int param) - { - } -} - -public interface I -{ - void Method1(int param); - void Method2(int param); -} - -public class ClassWithExtern -{ - [DllImport(""Dependency.dll"")] - public static extern void DllImportMethod(int param); - - public static extern void ExternalMethod(int param); -} -"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System -Imports System.Runtime.InteropServices - -Public MustInherit Class Derived - Inherits Base - Implements I - ' Override - Public Overrides Sub VirtualMethod(param As Integer) - End Sub - - ' Abstract - Public MustOverride Sub AbstractMethod(param As Integer) - - ' Explicit interface implementation - VB has no implicit interface implementation. - Public Sub Method1(param As Integer) Implements I.Method1 - End Sub - - ' Explicit interface implementation - Private Sub I_Method2(param As Integer) Implements I.Method2 - End Sub - - ' Event handlers - Public Sub MyEventHandler(o As Object, e As EventArgs) - End Sub - - Public Sub MyEventHandler2(o As Object, e As MyEventArgs) - End Sub - - Public Class MyEventArgs - Inherits EventArgs - End Class -End Class - -Public Class Base - ' Virtual - Public Overridable Sub VirtualMethod(param As Integer) - End Sub -End Class - -Public Interface I - Sub Method1(param As Integer) - Sub Method2(param As Integer) -End Interface - -Public Class ClassWithExtern - - Public Shared Sub DllImportMethod(param As Integer) - End Sub - - Public Declare Function DeclareFunction Lib ""Dependency.dll"" (param As Integer) As Integer -End Class -"); - } - - [Fact] - [WorkItem(459, "https://github.com/dotnet/roslyn-analyzers/issues/459")] - public async Task NoDiagnosticForMethodsWithSpecialAttributesTest() - { - await VerifyCS.VerifyAnalyzerAsync(@" -#define CONDITION_1 - -using System; -using System.Diagnostics; -using System.Runtime.Serialization; - -public class ConditionalMethodsClass -{ - [Conditional(""CONDITION_1"")] - private static void ConditionalMethod(int a) - { - AnotherConditionalMethod(a); - } - - [Conditional(""CONDITION_2"")] - private static void AnotherConditionalMethod(int b) - { - Console.WriteLine(b); - } -} - -public class SerializableMethodsClass -{ - [OnSerializing] - private void OnSerializingCallback(StreamingContext context) - { - Console.WriteLine(this); - } - - [OnSerialized] - private void OnSerializedCallback(StreamingContext context) - { - Console.WriteLine(this); - } - - [OnDeserializing] - private void OnDeserializingCallback(StreamingContext context) - { - Console.WriteLine(this); - } - - [OnDeserialized] - private void OnDeserializedCallback(StreamingContext context) - { - Console.WriteLine(this); - } -} -"); - - await VerifyVB.VerifyAnalyzerAsync(@" -#Const CONDITION_1 = 5 - -Imports System -Imports System.Diagnostics -Imports System.Runtime.Serialization - -Public Class ConditionalMethodsClass - _ - Private Shared Sub ConditionalMethod(a As Integer) - AnotherConditionalMethod(a) - End Sub - - _ - Private Shared Sub AnotherConditionalMethod(b As Integer) - Console.WriteLine(b) - End Sub -End Class - -Public Class SerializableMethodsClass - _ - Private Sub OnSerializingCallback(context As StreamingContext) - Console.WriteLine(Me) - End Sub - - _ - Private Sub OnSerializedCallback(context As StreamingContext) - Console.WriteLine(Me) - End Sub - - _ - Private Sub OnDeserializingCallback(context As StreamingContext) - Console.WriteLine(Me) - End Sub - - _ - Private Sub OnDeserializedCallback(context As StreamingContext) - Console.WriteLine(Me) - End Sub -End Class -"); - } - - [Fact, WorkItem(1218, "https://github.com/dotnet/roslyn-analyzers/issues/1218")] - public async Task NoDiagnosticForMethodsUsedAsDelegatesCSharp() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C1 -{ - private Action _handler; - - public void Handler(object o1) - { - } - - public void SetupHandler() - { - _handler = Handler; - } -} - -public class C2 -{ - public void Handler(object o1) - { - } - - public void TakesHandler(Action handler) - { - handler(null); - } - - public void SetupHandler() - { - TakesHandler(Handler); - } -} - -public class C3 -{ - private Action _handler; - - public C3() - { - _handler = Handler; - } - - public void Handler(object o1) - { - } -}"); - } - - [Fact, WorkItem(1218, "https://github.com/dotnet/roslyn-analyzers/issues/1218")] - public async Task NoDiagnosticForMethodsUsedAsDelegatesBasic() - { - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System -Public Class C1 - Private _handler As Action(Of Object) - - Public Sub Handler(o As Object) - End Sub - - Public Sub SetupHandler() - _handler = AddressOf Handler - End Sub -End Class - -Module M2 - Sub Handler(o As Object) - End Sub - - Sub TakesHandler(handler As Action(Of Object)) - handler(Nothing) - End Sub - - Sub SetupHandler() - TakesHandler(AddressOf Handler) - End Sub -End Module - -Class C3 - Private _handler As Action(Of Object) - - Sub New() - _handler = AddressOf Handler - End Sub - - Sub Handler(o As Object) - End Sub -End Class -"); - } - - [Fact, WorkItem(1218, "https://github.com/dotnet/roslyn-analyzers/issues/1218")] - public async Task NoDiagnosticForObsoleteMethods() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C1 -{ - [Obsolete] - public void ObsoleteMethod(object o1) - { - } -}"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System - -Public Class C1 - - Public Sub ObsoleteMethod(o1 as Object) - End Sub -End Class"); - } - - [Fact, WorkItem(1218, "https://github.com/dotnet/roslyn-analyzers/issues/1218")] - public async Task NoDiagnosticMethodJustThrowsNotImplemented() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class MyAttribute: Attribute -{ - public int X; - - public MyAttribute(int x) - { - X = x; - } -} -public class C1 -{ - public int Prop1 - { - get - { - throw new NotImplementedException(); - } - set - { - throw new NotImplementedException(); - } - } - - public void Method1(object o1) - { - throw new NotImplementedException(); - } - - public void Method2(object o1) => throw new NotImplementedException(); - - [MyAttribute(0)] - public void Method3(object o1) - { - throw new NotImplementedException(); - } -}"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System - -Public Class C1 - Property Prop1 As Integer - Get - Throw New NotImplementedException() - End Get - Set(ByVal value As Integer) - Throw New NotImplementedException() - End Set - End Property - - Public Sub Method1(o1 As Object) - Throw New NotImplementedException() - End Sub -End Class"); - } - - [Fact, WorkItem(1218, "https://github.com/dotnet/roslyn-analyzers/issues/1218")] - public async Task NoDiagnosticMethodJustThrowsNotSupported() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C1 -{ - public int Prop1 - { - get - { - throw new NotSupportedException(); - } - set - { - throw new NotSupportedException(); - } - } - - public void Method1(object o1) - { - throw new NotSupportedException(); - } - - public void Method2(object o1) => throw new NotSupportedException(); -}"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System - -Public Class C1 - Property Prop1 As Integer - Get - Throw New NotSupportedException() - End Get - Set(ByVal value As Integer) - Throw New NotSupportedException() - End Set - End Property - - Public Sub Method1(o1 As Object) - Throw New NotSupportedException() - End Sub -End Class"); - } - - [Fact] - public async Task NoDiagnosticsForIndexer() - { - await VerifyCS.VerifyAnalyzerAsync(@" -class C -{ - public int this[int i] - { - get { return 0; } - set { } - } -} -"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Class C - Public Property Item(i As Integer) As Integer - Get - Return 0 - End Get - - Set - End Set - End Property -End Class -"); - } - - [Fact] - public async Task NoDiagnosticsForPropertySetter() - { - await VerifyCS.VerifyAnalyzerAsync(@" -class C -{ - public int Property - { - get { return 0; } - set { } - } -} -"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Class C - Public Property Property1 As Integer - Get - Return 0 - End Get - - Set - End Set - End Property -End Class -"); - } - [Fact] - public async Task NoDiagnosticsForFirstParameterOfExtensionMethod() - { - await VerifyCS.VerifyAnalyzerAsync(@" -static class C -{ - static void ExtensionMethod(this int i) { } - static int ExtensionMethod(this int i, int anotherParam) { return anotherParam; } -} -"); - } - - [Fact] - public async Task NoDiagnosticsForSingleStatementMethodsWithDefaultParameters() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C -{ - public void SomeMethod(string p1, string p2 = null) - { - throw new NotImplementedException(); - } -} -"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System -Public Class C - Public Sub Test(p1 As String, Optional p2 As String = Nothing) - Throw New NotImplementedException() - End Sub -End Class"); - } - - [Fact] - [WorkItem(2589, "https://github.com/dotnet/roslyn-analyzers/issues/2589")] - [WorkItem(2593, "https://github.com/dotnet/roslyn-analyzers/issues/2593")] - public async Task NoDiagnosticDiscardParameterNames() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C -{ - public void M(int _, int _1, int _4) - { - } -} -"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System - -Public Class C - ' _ is not an allowed identifier in VB. - Public Sub M(_1 As Integer, _2 As Integer, _4 As Integer) - End Sub -End Class -"); - } - - [Fact] - [WorkItem(2466, "https://github.com/dotnet/roslyn-analyzers/issues/2466")] - public async Task NoDiagnosticUsedLocalFunctionParameters() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C -{ - public void M() - { - LocalFunction(0); - return; - - void LocalFunction(int x) - { - Console.WriteLine(x); - } - } -} -"); - } - - [Theory] - [WorkItem(1375, "https://github.com/dotnet/roslyn-analyzers/issues/1375")] - [InlineData("public", "dotnet_code_quality.api_surface = private", false)] - [InlineData("private", "dotnet_code_quality.api_surface = internal, public", false)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = internal, private", false)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = Friend, Private", false)] - [InlineData("public", "dotnet_code_quality.Usage.api_surface = internal, private", false)] - [InlineData("public", @"dotnet_code_quality.api_surface = all - dotnet_code_quality.CA1801.api_surface = private", false)] - [InlineData("public", "dotnet_code_quality.api_surface = public", true)] - [InlineData("public", "dotnet_code_quality.api_surface = internal, public", true)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = public", true)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = all", true)] - [InlineData("public", "dotnet_code_quality.Usage.api_surface = public, private", true)] - [InlineData("public", @"dotnet_code_quality.api_surface = all - dotnet_code_quality.CA1801.api_surface = public", true)] - public async Task EditorConfigConfiguration_ApiSurfaceOption_AsAdditionalDocument(string accessibility, string editorConfigText, bool expectDiagnostic) - { - var paramName = expectDiagnostic ? "[|unused|]" : "unused"; - - await new VerifyCS.Test - { - TestState = - { - Sources = - { - $@" -public class C -{{ - {accessibility} void M(int {paramName}) - {{ - }} -}}" - }, - AdditionalFiles = { (".editorconfig", editorConfigText) } - } - }.RunAsync(); - - await new VerifyVB.Test - { - TestState = - { - Sources = - { - $@" -Public Class C - {accessibility} Sub M({paramName} As Integer) - End Sub -End Class" - }, - AdditionalFiles = { (".editorconfig", editorConfigText) } - } - }.RunAsync(); - } - - [Theory] - [WorkItem(1375, "https://github.com/dotnet/roslyn-analyzers/issues/1375")] - [InlineData("public", "dotnet_code_quality.api_surface = private", false)] - [InlineData("private", "dotnet_code_quality.api_surface = internal, public", false)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = internal, private", false)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = Friend, Private", false)] - [InlineData("public", "dotnet_code_quality.Usage.api_surface = internal, private", false)] - [InlineData("public", @"dotnet_code_quality.api_surface = all - dotnet_code_quality.CA1801.api_surface = private", false)] - [InlineData("public", "dotnet_code_quality.api_surface = public", true)] - [InlineData("public", "dotnet_code_quality.api_surface = internal, public", true)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = public", true)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = all", true)] - [InlineData("public", "dotnet_code_quality.Usage.api_surface = public, private", true)] - [InlineData("public", @"dotnet_code_quality.api_surface = all - dotnet_code_quality.CA1801.api_surface = public", true)] - public async Task EditorConfigConfiguration_ApiSurfaceOption_AsAnalyzerConfigDocument(string accessibility, string editorConfigText, bool expectDiagnostic) - { - var csTest = new VerifyCS.Test - { - TestState = - { - Sources = - { - $@" -public class C -{{ - {accessibility} void M(int unused) - {{ - }} -}}" - } - }, - SolutionTransforms = { WithAnalyzerConfigDocument } - }; - - if (expectDiagnostic) - { - csTest.ExpectedDiagnostics.Add(VerifyCS.Diagnostic().WithSpan(@"z:\Test0.cs", 4, 23, 4, 29).WithArguments("unused", "M")); - } - - await csTest.RunAsync(); - - var vbTest = new VerifyVB.Test - { - TestState = - { - Sources = - { - $@" -Public Class C - {accessibility} Sub M(unused As Integer) - End Sub -End Class" - }, - }, - SolutionTransforms = { WithAnalyzerConfigDocument } - }; - - if (expectDiagnostic) - { - vbTest.ExpectedDiagnostics.Add(VerifyVB.Diagnostic().WithSpan(@"z:\Test0.vb", 3, 18, 3, 24).WithArguments("unused", "M")); - } - - await vbTest.RunAsync(); - return; - - Solution WithAnalyzerConfigDocument(Solution solution, ProjectId projectId) - { - var project = solution.GetProject(projectId)!; - var projectFilePath = project.Language == LanguageNames.CSharp ? @"z:\Test.csproj" : @"z:\Test.vbproj"; - solution = solution.WithProjectFilePath(projectId, projectFilePath); - - var documentId = project.DocumentIds.Single(); - var documentExtension = project.Language == LanguageNames.CSharp ? "cs" : "vb"; - solution = solution.WithDocumentFilePath(documentId, $@"z:\Test0.{documentExtension}"); - - return solution.GetProject(projectId)! - .AddAnalyzerConfigDocument( - ".editorconfig", - SourceText.From($"[*.{documentExtension}]" + Environment.NewLine + editorConfigText), - filePath: @"z:\.editorconfig") - .Project.Solution; - } - } - - [Theory] - [WorkItem(1375, "https://github.com/dotnet/roslyn-analyzers/issues/1375")] - [InlineData("public", "dotnet_code_quality.api_surface = private", false)] - [InlineData("private", "dotnet_code_quality.api_surface = internal, public", false)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = internal, private", false)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = Friend, Private", false)] - [InlineData("public", "dotnet_code_quality.Usage.api_surface = internal, private", false)] - [InlineData("public", @"dotnet_code_quality.api_surface = all - dotnet_code_quality.CA1801.api_surface = private", false)] - [InlineData("public", "dotnet_code_quality.api_surface = public", true)] - [InlineData("public", "dotnet_code_quality.api_surface = internal, public", true)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = public", true)] - [InlineData("public", "dotnet_code_quality.CA1801.api_surface = all", true)] - [InlineData("public", "dotnet_code_quality.Usage.api_surface = public, private", true)] - [InlineData("public", @"dotnet_code_quality.api_surface = all - dotnet_code_quality.CA1801.api_surface = public", true)] - public async Task EditorConfigConfiguration_ApiSurfaceOption_AsAnalyzerConfigDocumentAndAdditionalDocument(string accessibility, string editorConfigText, bool expectDiagnostic) - { - var csTest = new VerifyCS.Test - { - TestState = - { - Sources = - { - $@" -public class C -{{ - {accessibility} void M(int unused) - {{ - }} -}}" - }, - AdditionalFiles = { (".editorconfig", editorConfigText) } - }, - SolutionTransforms = { WithAnalyzerConfigDocument } - }; - - if (expectDiagnostic) - { - csTest.ExpectedDiagnostics.Add(VerifyCS.Diagnostic().WithSpan(@"z:\Test0.cs", 4, 23, 4, 29).WithArguments("unused", "M")); - } - - await csTest.RunAsync(); - - var vbTest = new VerifyVB.Test - { - TestState = - { - Sources = - { - $@" -Public Class C - {accessibility} Sub M(unused As Integer) - End Sub -End Class" - }, - AdditionalFiles = { (".editorconfig", editorConfigText) } - }, - SolutionTransforms = { WithAnalyzerConfigDocument } - }; - - if (expectDiagnostic) - { - vbTest.ExpectedDiagnostics.Add(VerifyVB.Diagnostic().WithSpan(@"z:\Test0.vb", 3, 18, 3, 24).WithArguments("unused", "M")); - } - - await vbTest.RunAsync(); - return; - - Solution WithAnalyzerConfigDocument(Solution solution, ProjectId projectId) - { - var project = solution.GetProject(projectId)!; - var projectFilePath = project.Language == LanguageNames.CSharp ? @"z:\Test.csproj" : @"z:\Test.vbproj"; - solution = solution.WithProjectFilePath(projectId, projectFilePath); - - var documentId = project.DocumentIds.Single(); - var documentExtension = project.Language == LanguageNames.CSharp ? "cs" : "vb"; - solution = solution.WithDocumentFilePath(documentId, $@"z:\Test0.{documentExtension}"); - - return solution.GetProject(projectId)! - .AddAnalyzerConfigDocument( - ".editorconfig", - SourceText.From($"[*.{documentExtension}]" + Environment.NewLine + editorConfigText), - filePath: @"z:\.editorconfig") - .Project.Solution; - } - } - - [Fact, WorkItem(3106, "https://github.com/dotnet/roslyn-analyzers/issues/3106")] - public async Task EventArgsNotInheritingFromSystemEventArgs_NoDiagnostic() - { - await VerifyCS.VerifyAnalyzerAsync(@" -// Reproduce UWP some specific EventArgs -namespace Windows.UI.Xaml -{ - public class RoutedEventArgs {} - public class SizeChangedEventArgs : RoutedEventArgs {} - public class WindowCreatedEventArgs {} -} - -namespace SomeNamespace -{ - public class MyCustomEventArgs {} -} - -public class C -{ - private void Page_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) {} - private void OnSizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e) {} - private void OnWindowCreated(object sender, Windows.UI.Xaml.WindowCreatedEventArgs e) {} - - private void OnSomething(object sender, SomeNamespace.MyCustomEventArgs e) {} -}"); - - await VerifyVB.VerifyAnalyzerAsync(@" -' Reproduce UWP some specific EventArgs -Namespace Windows.UI.Xaml - Public Class RoutedEventArgs - End Class - - Public Class SizeChangedEventArgs - Inherits RoutedEventArgs - End Class - - Public Class WindowCreatedEventArgs - End Class -End Namespace - -Namespace SomeNamespace - Public Class MyCustomEventArgs - End Class -End Namespace - -Public Class C - Private Sub Page_Loaded(ByVal sender As Object, ByVal e As Windows.UI.Xaml.RoutedEventArgs) - End Sub - - Private Sub OnSizeChanged(ByVal sender As Object, ByVal e As Windows.UI.Xaml.SizeChangedEventArgs) - End Sub - - Private Sub OnWindowCreated(ByVal sender As Object, ByVal e As Windows.UI.Xaml.WindowCreatedEventArgs) - End Sub - - Private Sub OnSomething(ByVal sender As Object, ByVal e As SomeNamespace.MyCustomEventArgs) - End Sub -End Class"); - } - - [Fact, WorkItem(3039, "https://github.com/dotnet/roslyn-analyzers/issues/3039")] - public async Task SerializationConstructorParameters_NoDiagnostic() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; -using System.Runtime.Serialization; - -public class C -{ - protected C(SerializationInfo info, StreamingContext context) - { - } -}"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System.Runtime.Serialization - -Public Class C - Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) - End Sub -End Class"); - } - - [Fact, WorkItem(3039, "https://github.com/dotnet/roslyn-analyzers/issues/3039")] - public async Task GetObjectDataParameters_NoDiagnostic() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; -using System.Runtime.Serialization; - -public class C -{ - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - } -}"); - - await VerifyVB.VerifyAnalyzerAsync(@" -Imports System.Runtime.Serialization - -Public Class C - Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) - End Sub -End Class"); - } - - [Fact] - [WorkItem(2846, "https://github.com/dotnet/roslyn-analyzers/issues/2846")] - public async Task CA1801_MethodThrowArrowExpression_NoDiagnostic() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class Class1 -{ - public int Method1(int value) => throw new NotImplementedException(); -} -"); - } - - [Fact, WorkItem(4052, "https://github.com/dotnet/roslyn-analyzers/issues/4052")] - public async Task CA1801_TopLevelStatements_NoDiagnostic() - { - await new VerifyCS.Test() - { - TestCode = @"int x = 0;", - LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp9, - SolutionTransforms = - { - (solution, projectId) => - { - var project = solution.GetProject(projectId); - project = project.WithCompilationOptions(project.CompilationOptions.WithOutputKind(OutputKind.ConsoleApplication)); - return project.Solution; - }, - } - }.RunAsync(); - } - - [Fact, WorkItem(4462, "https://github.com/dotnet/roslyn-analyzers/issues/4462")] - public async Task CA1801_CSharp_ImplicitRecord() - { - await new VerifyCS.Test - { - LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp9, - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, - TestCode = @" -public record Person(string Name, int Age = 0); - -public record Person2(string Name, int Age = 0) {}", - }.RunAsync(); - } - - #endregion - - #region Unit tests for analyzer diagnostic(s) - - [Fact] - [WorkItem(459, "https://github.com/dotnet/roslyn-analyzers/issues/459")] - public async Task CSharp_DiagnosticForSimpleCasesTest() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -class C -{ - public C(int param) - { - } - - public void UnusedParamMethod(int param) - { - } - - public static void UnusedParamStaticMethod(int param1) - { - } - - public void UnusedDefaultParamMethod(int defaultParam = 1) - { - } - - public void UnusedParamsArrayParamMethod(params int[] paramsArr) - { - } - - public void MultipleUnusedParamsMethod(int param1, int param2) - { - } - - private void UnusedRefParamMethod(ref int param1) - { - } - - public void UnusedErrorTypeParamMethod({|CS0246:UndefinedType|} param1) - { - } -} -", - GetCSharpUnusedParameterResultAt(6, 18, "param", ".ctor"), - GetCSharpUnusedParameterResultAt(10, 39, "param", "UnusedParamMethod"), - GetCSharpUnusedParameterResultAt(14, 52, "param1", "UnusedParamStaticMethod"), - GetCSharpUnusedParameterResultAt(18, 46, "defaultParam", "UnusedDefaultParamMethod"), - GetCSharpUnusedParameterResultAt(22, 59, "paramsArr", "UnusedParamsArrayParamMethod"), - GetCSharpUnusedParameterResultAt(26, 48, "param1", "MultipleUnusedParamsMethod"), - GetCSharpUnusedParameterResultAt(26, 60, "param2", "MultipleUnusedParamsMethod"), - GetCSharpUnusedParameterResultAt(30, 47, "param1", "UnusedRefParamMethod"), - GetCSharpUnusedParameterResultAt(34, 58, "param1", "UnusedErrorTypeParamMethod")); - } - - [Fact] - [WorkItem(459, "https://github.com/dotnet/roslyn-analyzers/issues/459")] - public async Task Basic_DiagnosticForSimpleCasesTest() - { - await VerifyVB.VerifyAnalyzerAsync(@" -Class C - Public Sub New(param As Integer) - End Sub - - Public Sub UnusedParamMethod(param As Integer) - End Sub - - Public Shared Sub UnusedParamStaticMethod(param1 As Integer) - End Sub - - Public Sub UnusedDefaultParamMethod(Optional defaultParam As Integer = 1) - End Sub - - Public Sub UnusedParamsArrayParamMethod(ParamArray paramsArr As Integer()) - End Sub - - Public Sub MultipleUnusedParamsMethod(param1 As Integer, param2 As Integer) - End Sub - - Private Sub UnusedRefParamMethod(ByRef param1 As Integer) - End Sub - - Public Sub UnusedErrorTypeParamMethod(param1 As {|BC30002:UndefinedType|}) - End Sub -End Class -", - GetBasicUnusedParameterResultAt(3, 20, "param", ".ctor"), - GetBasicUnusedParameterResultAt(6, 34, "param", "UnusedParamMethod"), - GetBasicUnusedParameterResultAt(9, 47, "param1", "UnusedParamStaticMethod"), - GetBasicUnusedParameterResultAt(12, 50, "defaultParam", "UnusedDefaultParamMethod"), - GetBasicUnusedParameterResultAt(15, 56, "paramsArr", "UnusedParamsArrayParamMethod"), - GetBasicUnusedParameterResultAt(18, 43, "param1", "MultipleUnusedParamsMethod"), - GetBasicUnusedParameterResultAt(18, 62, "param2", "MultipleUnusedParamsMethod"), - GetBasicUnusedParameterResultAt(21, 44, "param1", "UnusedRefParamMethod"), - GetBasicUnusedParameterResultAt(24, 43, "param1", "UnusedErrorTypeParamMethod")); - } - - [Fact] - public async Task DiagnosticsForNonFirstParameterOfExtensionMethod() - { - await VerifyCS.VerifyAnalyzerAsync(@" -static class C -{ - static void ExtensionMethod(this int i, int anotherParam) { } -} -", - GetCSharpUnusedParameterResultAt(4, 49, "anotherParam", "ExtensionMethod")); - } - - [Fact] - [WorkItem(2466, "https://github.com/dotnet/roslyn-analyzers/issues/2466")] - public async Task DiagnosticForUnusedLocalFunctionParameters_01() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C -{ - public void M() - { - LocalFunction(0); - return; - - void LocalFunction(int x) - { - } - } -}", - GetCSharpUnusedParameterResultAt(11, 32, "x", "LocalFunction")); - } - - [Fact] - [WorkItem(2466, "https://github.com/dotnet/roslyn-analyzers/issues/2466")] - public async Task DiagnosticForUnusedLocalFunctionParameters_02() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C -{ - public void M() - { - // Flag unused parameter even if LocalFunction is unused. - void LocalFunction(int x) - { - } - } -}", - GetCSharpUnusedParameterResultAt(9, 32, "x", "LocalFunction")); - } - - [Fact] - public async Task DiagnosticForMethodsInNestedTypes() - { - await VerifyCS.VerifyAnalyzerAsync(@" -using System; - -public class C -{ - public void OuterM(int [|x|]) - { - } - - public class NestedType - { - public void InnerM(int [|y|]) - { - } - } -}"); - } - - [Fact, WorkItem(4462, "https://github.com/dotnet/roslyn-analyzers/issues/4462")] - public async Task CA1801_CSharp_Record() - { - await new VerifyCS.Test - { - LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp9, - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, - TestCode = @" -public record OtherPerson -{ - public string Name { get; init; } - - public OtherPerson(string name, int [|age|] = 0) - => Name = name; -}", - }.RunAsync(); - } - - #endregion - - #region Helpers - - private static DiagnosticResult GetCSharpUnusedParameterResultAt(int line, int column, string parameterName, string methodName) - => VerifyCS.Diagnostic() - .WithLocation(line, column) - .WithArguments(parameterName, methodName); - - private static DiagnosticResult GetBasicUnusedParameterResultAt(int line, int column, string parameterName, string methodName) - => VerifyVB.Diagnostic() - .WithLocation(line, column) - .WithArguments(parameterName, methodName); - - #endregion - } -} diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.CodeQuality.Analyzers/Maintainability/BasicReviewUnusedParameters.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.CodeQuality.Analyzers/Maintainability/BasicReviewUnusedParameters.Fixer.vb deleted file mode 100644 index f25fb1b220..0000000000 --- a/src/NetAnalyzers/VisualBasic/Microsoft.CodeQuality.Analyzers/Maintainability/BasicReviewUnusedParameters.Fixer.vb +++ /dev/null @@ -1,26 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports System.Composition -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeQuality.Analyzers.Maintainability - -Namespace Microsoft.CodeQuality.VisualBasic.Analyzers.Maintainability - ''' - ''' CA1801: Review unused parameters - ''' - - Public NotInheritable Class BasicReviewUnusedParametersFixer - Inherits ReviewUnusedParametersFixer - - Protected Overrides Function GetParameterDeclarationNode(node As SyntaxNode) As SyntaxNode - Return node.Parent - End Function - - Protected Overrides Function CanContinuouslyLeadToObjectCreationOrInvocation(node As SyntaxNode) As Boolean - Dim kind = node.Kind() - Return kind = SyntaxKind.QualifiedName OrElse kind = SyntaxKind.IdentifierName OrElse kind = SyntaxKind.SimpleMemberAccessExpression - End Function - End Class -End Namespace \ No newline at end of file diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.CodeQuality.Analyzers/Maintainability/BasicReviewUnusedParametersAnalyzer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.CodeQuality.Analyzers/Maintainability/BasicReviewUnusedParametersAnalyzer.vb deleted file mode 100644 index 8aacd1236f..0000000000 --- a/src/NetAnalyzers/VisualBasic/Microsoft.CodeQuality.Analyzers/Maintainability/BasicReviewUnusedParametersAnalyzer.vb +++ /dev/null @@ -1,20 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeQuality.Analyzers.Maintainability - -Namespace Microsoft.CodeQuality.VisualBasic.Analyzers.Maintainability - - ''' - ''' CA1801: Review unused parameters - ''' - - Public NotInheritable Class BasicReviewUnusedParametersAnalyzer - Inherits ReviewUnusedParametersAnalyzer - - Protected Overrides Function IsPositionalRecordPrimaryConstructor(methodSymbol As IMethodSymbol) As Boolean - Return False - End Function - End Class -End Namespace \ No newline at end of file From 1c852e3a69d5c8a60ebe599d7962b8efd16d9bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 9 Dec 2020 11:01:18 +0100 Subject: [PATCH 003/224] Add CA1801 to removed rules list --- src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index cdf4f1397e..f7dbd19c06 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1 +1,5 @@ -; Please do not edit this file manually, it should only be updated through code fix application. +### Removed Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +CA1801 | Usage | Disabled | ReviewUnusedParametersAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1801) From 7f39ec87ebcbef5130c5efff84b8cae9c015f66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 9 Dec 2020 12:08:50 +0100 Subject: [PATCH 004/224] Fix md and sarif files --- .../Microsoft.CodeAnalysis.FxCopAnalyzers.md | 12 ------ ...icrosoft.CodeAnalysis.FxCopAnalyzers.sarif | 40 ------------------- .../Microsoft.CodeQuality.Analyzers.md | 12 ------ .../Microsoft.CodeQuality.Analyzers.sarif | 40 ------------------- 4 files changed, 104 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.md b/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.md index 626f4037ca..ae4484b509 100644 --- a/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.md +++ b/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.md @@ -972,18 +972,6 @@ Consistent naming of parameters in an override hierarchy increases the usability |CodeFix|True| --- -## [CA1801](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801): Review unused parameters - -Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names. - -|Item|Value| -|-|-| -|Category|Usage| -|Enabled|True| -|Severity|Warning| -|CodeFix|True| ---- - ## [CA1802](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1802): Use literals where appropriate A field is declared static and read-only (Shared and ReadOnly in Visual Basic), and is initialized by using a value that is computable at compile time. Because the value that is assigned to the targeted field is computable at compile time, change the declaration to a const (Const in Visual Basic) field so that the value is computed at compile time instead of at run?time. diff --git a/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.sarif b/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.sarif index ed533befff..b3a5eca27e 100644 --- a/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.sarif +++ b/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.sarif @@ -2031,26 +2031,6 @@ ] } }, - "CA1801": { - "id": "CA1801", - "shortDescription": "Review unused parameters", - "fullDescription": "Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names.", - "defaultLevel": "warning", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801", - "properties": { - "category": "Usage", - "isEnabledByDefault": true, - "typeName": "CSharpReviewUnusedParametersAnalyzer", - "languages": [ - "C#" - ], - "tags": [ - "PortedFromFxCop", - "Telemetry", - "EnabledRuleInAggressiveMode" - ] - } - }, "CA1812": { "id": "CA1812", "shortDescription": "Avoid uninstantiated internal classes", @@ -2219,26 +2199,6 @@ ] } }, - "CA1801": { - "id": "CA1801", - "shortDescription": "Review unused parameters", - "fullDescription": "Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names.", - "defaultLevel": "warning", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801", - "properties": { - "category": "Usage", - "isEnabledByDefault": true, - "typeName": "BasicReviewUnusedParametersAnalyzer", - "languages": [ - "Visual Basic" - ], - "tags": [ - "PortedFromFxCop", - "Telemetry", - "EnabledRuleInAggressiveMode" - ] - } - }, "CA1812": { "id": "CA1812", "shortDescription": "Avoid uninstantiated internal classes", diff --git a/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md b/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md index 4a3acc7b11..d26098b3ca 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md +++ b/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md @@ -840,18 +840,6 @@ Consistent naming of parameters in an override hierarchy increases the usability |CodeFix|True| --- -## [CA1801](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801): Review unused parameters - -Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names. - -|Item|Value| -|-|-| -|Category|Usage| -|Enabled|True| -|Severity|Warning| -|CodeFix|True| ---- - ## [CA1802](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1802): Use literals where appropriate A field is declared static and read-only (Shared and ReadOnly in Visual Basic), and is initialized by using a value that is computable at compile time. Because the value that is assigned to the targeted field is computable at compile time, change the declaration to a const (Const in Visual Basic) field so that the value is computed at compile time instead of at run?time. diff --git a/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.sarif b/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.sarif index 1385dd213f..09971cd0af 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.sarif +++ b/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.sarif @@ -2016,26 +2016,6 @@ ] } }, - "CA1801": { - "id": "CA1801", - "shortDescription": "Review unused parameters", - "fullDescription": "Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names.", - "defaultLevel": "warning", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801", - "properties": { - "category": "Usage", - "isEnabledByDefault": true, - "typeName": "CSharpReviewUnusedParametersAnalyzer", - "languages": [ - "C#" - ], - "tags": [ - "PortedFromFxCop", - "Telemetry", - "EnabledRuleInAggressiveMode" - ] - } - }, "CA1812": { "id": "CA1812", "shortDescription": "Avoid uninstantiated internal classes", @@ -2204,26 +2184,6 @@ ] } }, - "CA1801": { - "id": "CA1801", - "shortDescription": "Review unused parameters", - "fullDescription": "Avoid unused paramereters in your code. If the parameter cannot be removed, then change its name so it starts with an underscore and is optionally followed by an integer, such as '_', '_1', '_2', etc. These are treated as special discard symbol names.", - "defaultLevel": "warning", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801", - "properties": { - "category": "Usage", - "isEnabledByDefault": true, - "typeName": "BasicReviewUnusedParametersAnalyzer", - "languages": [ - "Visual Basic" - ], - "tags": [ - "PortedFromFxCop", - "Telemetry", - "EnabledRuleInAggressiveMode" - ] - } - }, "CA1812": { "id": "CA1812", "shortDescription": "Avoid uninstantiated internal classes", From 202c1edb60ff56688fbd6a214ab7e3c26d3ef519 Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Fri, 8 Jan 2021 14:29:08 -0800 Subject: [PATCH 005/224] Remove single-file analyzer --- .../Core/AnalyzerReleases.Unshipped.md | 8 +- .../MicrosoftNetCoreAnalyzersResources.resx | 9 - .../AvoidAssemblyLocationInSingleFile.cs | 161 ----------------- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 15 -- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 15 -- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 15 -- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 15 -- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 15 -- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 24 --- .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 40 ----- .../AvoidAssemblyLocationInSingleFileTests.cs | 167 ------------------ src/Utilities/Compiler/DiagnosticCategory.cs | 1 - .../DiagnosticCategoryAndIdRanges.txt | 6 +- .../Options/MSBuildPropertyOptionNames.cs | 2 - 22 files changed, 8 insertions(+), 605 deletions(-) delete mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Publish/AvoidAssemblyLocationInSingleFile.cs delete mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Publish/AvoidAssemblyLocationInSingleFileTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 0058139817..5f3c6ac8fb 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1 +1,7 @@ -; Please do not edit this file manually, it should only be updated through code fix application. \ No newline at end of file +; Please do not edit this file manually, it should only be updated through code fix application. + +### Removed Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +IL3000 | Public | Warning | Moved analyzer to mono/linker +IL3001 | Public | Warning | Moved analyzer to mono/linker \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index e240378b84..0b8e62a9ba 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1463,15 +1463,6 @@ Avoid 'StringBuilder' parameters for P/Invokes - - Avoid using accessing Assembly file path when publishing as a single-file - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - - - '{0}' will throw for assemblies embedded in a single-file app - This call site is reachable on: {2}. '{0}' is only supported on: {1}. This call site is reachable on: 'windows' all versions.'SupportedOnWindowsUnsupportedFromWindows2004()' is only supported on: 'windows' 10.0.2004 and before diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Publish/AvoidAssemblyLocationInSingleFile.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Publish/AvoidAssemblyLocationInSingleFile.cs deleted file mode 100644 index 505f561c5c..0000000000 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Publish/AvoidAssemblyLocationInSingleFile.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using Analyzer.Utilities; -using Analyzer.Utilities.Extensions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Operations; - -namespace Microsoft.NetCore.Analyzers.Publish -{ - /// - /// IL3000, IL3001: Do not use Assembly file path in single-file publish - /// - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public sealed class AvoidAssemblyLocationInSingleFile : DiagnosticAnalyzer - { - public const string IL3000 = nameof(IL3000); - public const string IL3001 = nameof(IL3001); - - internal static DiagnosticDescriptor LocationRule = DiagnosticDescriptorHelper.Create( - IL3000, - new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.AvoidAssemblyLocationInSingleFileTitle), - MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)), - new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.AvoidAssemblyLocationInSingleFileMessage), - MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)), - DiagnosticCategory.Publish, - RuleLevel.BuildWarning, - description: null, - isPortedFxCopRule: false, - isDataflowRule: false); - - internal static DiagnosticDescriptor GetFilesRule = DiagnosticDescriptorHelper.Create( - IL3001, - new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.AvoidAssemblyLocationInSingleFileTitle), - MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)), - new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.AvoidAssemblyGetFilesInSingleFileMessage), - MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)), - DiagnosticCategory.Publish, - RuleLevel.BuildWarning, - description: null, - isPortedFxCopRule: false, - isDataflowRule: false); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(LocationRule, GetFilesRule); - - public override void Initialize(AnalysisContext context) - { - context.EnableConcurrentExecution(); - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics); - - context.RegisterCompilationStartAction(context => - { - var compilation = context.Compilation; - var isSingleFilePublish = context.Options.GetMSBuildPropertyValue( - MSBuildPropertyOptionNames.PublishSingleFile, compilation, context.CancellationToken); - if (!string.Equals(isSingleFilePublish?.Trim(), "true", StringComparison.OrdinalIgnoreCase)) - { - return; - } - var includesAllContent = context.Options.GetMSBuildPropertyValue( - MSBuildPropertyOptionNames.IncludeAllContentForSelfExtract, compilation, context.CancellationToken); - if (string.Equals(includesAllContent?.Trim(), "true", StringComparison.OrdinalIgnoreCase)) - { - return; - } - - var propertiesBuilder = ImmutableArray.CreateBuilder(); - var methodsBuilder = ImmutableArray.CreateBuilder(); - - if (compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReflectionAssembly, out var assemblyType)) - { - // properties - AddIfNotNull(propertiesBuilder, TryGetSingleSymbol(assemblyType.GetMembers("Location"))); - - // methods - methodsBuilder.AddRange(assemblyType.GetMembers("GetFile").OfType()); - methodsBuilder.AddRange(assemblyType.GetMembers("GetFiles").OfType()); - } - - if (compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReflectionAssemblyName, out var assemblyNameType)) - { - AddIfNotNull(propertiesBuilder, TryGetSingleSymbol(assemblyNameType.GetMembers("CodeBase"))); - AddIfNotNull(propertiesBuilder, TryGetSingleSymbol(assemblyNameType.GetMembers("EscapedCodeBase"))); - } - - var properties = propertiesBuilder.ToImmutable(); - var methods = methodsBuilder.ToImmutable(); - - context.RegisterOperationAction(operationContext => - { - var access = (IPropertyReferenceOperation)operationContext.Operation; - var property = access.Property; - if (!Contains(properties, property, SymbolEqualityComparer.Default)) - { - return; - } - - operationContext.ReportDiagnostic(access.CreateDiagnostic(LocationRule, property)); - }, OperationKind.PropertyReference); - - context.RegisterOperationAction(operationContext => - { - var invocation = (IInvocationOperation)operationContext.Operation; - var targetMethod = invocation.TargetMethod; - if (!Contains(methods, targetMethod, SymbolEqualityComparer.Default)) - { - return; - } - - operationContext.ReportDiagnostic(invocation.CreateDiagnostic(GetFilesRule, targetMethod)); - }, OperationKind.Invocation); - - return; - - static bool Contains(ImmutableArray list, T elem, TComp comparer) - where TComp : IEqualityComparer - { - foreach (var e in list) - { - if (comparer.Equals(e, elem)) - { - return true; - } - } - return false; - } - - static TSymbol? TryGetSingleSymbol(ImmutableArray members) where TSymbol : class, ISymbol - { - TSymbol? candidate = null; - foreach (var m in members) - { - if (m is TSymbol tsym) - { - if (candidate is null) - { - candidate = tsym; - } - else - { - return null; - } - } - } - return candidate; - } - - static void AddIfNotNull(ImmutableArray.Builder properties, TSymbol? p) where TSymbol : class, ISymbol - { - if (p is not null) - { - properties.Add(p); - } - } - }); - } - } -} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index be063a9c1a..714e9e49b4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -47,21 +47,6 @@ Literály řetězců atributů by se měly správně parsovat - - '{0}' will throw for assemblies embedded in a single-file app - Pro sestavení vložená do aplikace s jedním souborem {0} vyvolá výjimku. - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - Pro sestavení vložená do aplikace s jedním souborem {0} vždy vrátí prázdný řetězec. Pokud se cesta k adresáři aplikace vyžaduje, zvažte možnost zavolat System.AppContext.BaseDirectory. - - - - Avoid using accessing Assembly file path when publishing as a single-file - Při publikování jednoho souboru nepoužívat přístup k cestě souboru sestavení - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Zařazením parametru StringBuilder se vždy vytvoří kopie nativní vyrovnávací paměti, která bude mít za následek vícenásobné přidělení pro jednu operaci zařazování. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 52ca3a058f..6e0f153a85 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -47,21 +47,6 @@ Attributzeichenfolgenliterale müssen richtig analysiert werden - - '{0}' will throw for assemblies embedded in a single-file app - "{0}" wird für Assemblys ausgelöst, die in einer Einzeldatei-App eingebettet sind. - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - "{0}" gibt immer eine leere Zeichenfolge für Assemblys zurück, die in einer Einzeldatei-App eingebettet sind. Wenn der Pfad zum App-Verzeichnis erforderlich ist, sollten Sie "System.AppContext.BaseDirectory" aufrufen. - - - - Avoid using accessing Assembly file path when publishing as a single-file - Zugriff auf den Assemblydateipfad beim Veröffentlichen als Einzeldatei vermeiden - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Beim Marshalling von "StringBuilder" wird immer eine native Pufferkopie erstellt, sodass mehrere Zuordnungen für einen Marshallingvorgang vorhanden sind. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 18668ee783..8c66938819 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -47,21 +47,6 @@ Los literales de cadena de atributo se deben analizar correctamente - - '{0}' will throw for assemblies embedded in a single-file app - Se iniciará "{0}" para los ensamblados insertados en una aplicación de un solo archivo. - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - "{0}" devuelve siempre una cadena vacía para los ensamblados insertados en una aplicación de un solo archivo. Si se necesita la ruta de acceso al directorio de la aplicación, considere la posibilidad de llamar a "System.AppContext.BaseDirectory". - - - - Avoid using accessing Assembly file path when publishing as a single-file - Evite usar la ruta de acceso al archivo de ensamblado al publicar como único archivo - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Al serializar "StringBuilder" siempre se crea una copia del búfer nativo, lo que da lugar a varias asignaciones para una operación de serialización. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index d90f9ec3af..71a0c15261 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -47,21 +47,6 @@ Les littéraux de chaîne d'attribut doivent être analysés correctement - - '{0}' will throw for assemblies embedded in a single-file app - '{0}' lèvera une exception pour les assemblys incorporés dans une application monofichier - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - '{0}' retourne toujours une chaîne vide pour les assemblys incorporés dans une application monofichier. Si le chemin du répertoire d'application est nécessaire, appelez 'System.AppContext.BaseDirectory'. - - - - Avoid using accessing Assembly file path when publishing as a single-file - Évitez d'utiliser l'accès du chemin du fichier d'assembly lors de la publication en tant qu'application monofichier - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Le marshaling de 'StringBuilder' crée toujours une copie de la mémoire tampon native, ce qui entraîne plusieurs allocations pour une seule opération de marshaling. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 09c16a9f6e..61adbbacb6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -47,21 +47,6 @@ I valori letterali stringa dell'attributo devono essere analizzati correttamente - - '{0}' will throw for assemblies embedded in a single-file app - '{0}' verrà generato per gli assembly incorporati in un'app a file singolo - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - '{0}' restituisce sempre una stringa vuota per gli assembly incorporati in un'app a file singolo. Se il percorso della directory app è necessario, provare a chiamare 'System.AppContext.BaseDirectory'. - - - - Avoid using accessing Assembly file path when publishing as a single-file - Evitare l'accesso al percorso del file di assembly quando si esegue la pubblicazione come file singolo - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Il marshalling di 'StringBuilder' crea sempre una copia del buffer nativo, di conseguenza vengono generate più allocazioni per una singola operazione di marshalling. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 6937c685a2..4faf2ebe51 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -47,21 +47,6 @@ 属性文字列リテラルは、正しく解析する必要があります - - '{0}' will throw for assemblies embedded in a single-file app - '{0}' では、単一ファイル アプリに組み込まれているアセンブリのためにスローします - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - '{0}' は、単一ファイル アプリに埋め込まれているアセンブリに対して、常に空の文字列を返します。アプリ ディレクトリへのパスが必要な場合は、'System.AppContext.BaseDirectory' を呼び出すことを検討してください。 - - - - Avoid using accessing Assembly file path when publishing as a single-file - 単一ファイルとして発行するときにアセンブリ ファイル パスへのアクセスを使用しない - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder' をマーシャリングすると、ネイティブ バッファーのコピーが常に作成され、1 回のマーシャリング操作に対して複数の割り当てが発生します。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index d329428df3..42e160e005 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -47,21 +47,6 @@ 특성 문자열 리터럴이 올바르게 구문 분석되어야 합니다. - - '{0}' will throw for assemblies embedded in a single-file app - '{0}'이(가) 단일 파일 앱에 포함된 어셈블리에 대해 throw합니다. - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - '{0}'은(는) 단일 파일 앱에 포함된 어셈블리에 대해 항상 빈 문자열을 반환합니다. 앱 디렉터리 경로가 필요한 경우 'System.AppContext.BaseDirectory'를 호출하는 것이 좋습니다. - - - - Avoid using accessing Assembly file path when publishing as a single-file - 단일 파일로 게시할 때 어셈블리 파일 경로 액세스를 사용하지 마세요. - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder'를 마샬링하는 경우 항상 네이티브 버퍼 복사본이 만들어지므로 하나의 마샬링 작업에 대해 할당이 여러 번 이루어집니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 0131fbd52a..dd9ad6db14 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -47,21 +47,6 @@ Analiza literałów ciągu atrybutu powinna kończyć się powodzeniem - - '{0}' will throw for assemblies embedded in a single-file app - Element „{0}” będzie zgłaszać dla zestawów osadzonych w aplikacji jednoplikowej - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - Element „{0}” zawsze zwraca pusty ciąg dla zestawów osadzonych w aplikacji jednoplikowej. Jeśli potrzebna jest ścieżka do katalogu aplikacji, rozważ wywołanie elementu „System.AppContext.BaseDirectory”. - - - - Avoid using accessing Assembly file path when publishing as a single-file - Unikaj uzyskiwania dostępu do ścieżki pliku zestawu podczas publikowania w postaci jednoplikowej - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Marshalling elementu „StringBuilder” zawsze tworzy natywną kopię buforu, co powoduje powstanie wielu alokacji dla jednej operacji marshallingu. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 9e63b6fc06..f73645c162 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -47,21 +47,6 @@ Literais de cadeias de caracteres de atributos devem ser analisados corretamente - - '{0}' will throw for assemblies embedded in a single-file app - '{0}' será gerado para assemblies inseridos em um aplicativo de arquivo único - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - '{0}' sempre retorna uma cadeia de caracteres vazia para assemblies inseridos em um aplicativo de arquivo único. Se o caminho para o diretório do aplicativo for necessário, considere chamar 'System.AppContext.BaseDirectory'. - - - - Avoid using accessing Assembly file path when publishing as a single-file - Evite usar o acesso ao Caminho do arquivo do assembly na publicação como um arquivo único - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. O marshaling de 'StringBuilder' sempre cria uma cópia de buffer nativo, resultando em várias alocações para uma operação de marshalling. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 6dbafe9c23..3b853eaf7e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -47,21 +47,6 @@ Синтаксический анализ строковых литералов атрибута должен осуществляться правильно - - '{0}' will throw for assemblies embedded in a single-file app - Для сборок, внедренных в приложение с одним файлом, будет генерироваться "{0}". - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - "{0}" всегда возвращает пустую строку для сборок, внедренных в приложение с одним файлом. Если требуется путь к каталогу приложения, попробуйте вызвать "System.AppContext.BaseDirectory". - - - - Avoid using accessing Assembly file path when publishing as a single-file - Не используйте путь к файлу сборки при публикации в виде одного файла - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. При маршалировании "StringBuilder" всегда создается собственная копия буфера, что приводит к множественным выделениям для одной операции маршалирования. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index c4dd09e3bd..e85849b540 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -47,21 +47,6 @@ Öznitelik dizesinin sabit değerleri doğru ayrıştırılmalıdır - - '{0}' will throw for assemblies embedded in a single-file app - '{0}', tek dosyalı bir uygulamaya eklenen bütünleştirilmiş kodlar için özel durum oluşturur - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - '{0}', tek dosyalı bir uygulamaya eklenen bütünleştirilmiş kodlar için her zaman boş bir dize döndürür. Uygulama dizininin yolu gerekiyorsa 'System.AppContext.BaseDirectory' özelliğini çağırmayı düşünün. - - - - Avoid using accessing Assembly file path when publishing as a single-file - Tek dosyalı olarak yayımlarken bütünleştirilmiş kod dosyası yoluna erişimi kullanmaktan kaçının - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder' öğesinin hazırlanması her zaman, bir hazırlama işlemi için birden çok ayırmaya neden olan yerel arabellek kopyası oluşturur. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 1a161cab86..2548f9f6f3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -47,21 +47,6 @@ 特性字符串文本应正确分析 - - '{0}' will throw for assemblies embedded in a single-file app - 对于嵌入在单文件应用中的程序集,将引发“{0}” - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - 对于嵌入在单文件应用中的程序集,“{0}”始终返回空字符串。如果需要应用目录的路径,请考虑调用 "System.AppContext.BaseDirectory"。 - - - - Avoid using accessing Assembly file path when publishing as a single-file - 在以单文件形式发布时,避免使用访问程序集文件路径 - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. "StringBuilder" 的封送处理总是会创建一个本机缓冲区副本,这导致一个封送处理操作出现多次分配。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 7bd5957db2..1aebe6df07 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -47,21 +47,6 @@ 屬性字串常值應正確剖析 - - '{0}' will throw for assemblies embedded in a single-file app - '{0}' 將會為內嵌在單一檔案應用程式中的組件進行擲回 - - - - '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - '{0}' 一律會為內嵌在單一檔案應用程式中的組件傳回空字串。若需要應用程式目錄的路徑,請考慮呼叫 'System.AppContext.BaseDirectory'。 - - - - Avoid using accessing Assembly file path when publishing as a single-file - 以單一檔案形式發佈時,避免使用正在存取的組件檔案路徑 - - Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 封送處理 'StringBuilder' 一律都會建立原生緩衝區複本,因而導致單一封送處理作業出現多重配置。 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 1dff4237dc..7fe18e9c6a 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -2927,27 +2927,3 @@ Hard-coded certificates in source code are vulnerable to being exploited. |Severity|Warning| |CodeFix|False| --- - -## [IL3000](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3000): Avoid using accessing Assembly file path when publishing as a single-file - -'{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - -|Item|Value| -|-|-| -|Category|Publish| -|Enabled|True| -|Severity|Warning| -|CodeFix|False| ---- - -## [IL3001](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3001): Avoid using accessing Assembly file path when publishing as a single-file - -'{0}' will throw for assemblies embedded in a single-file app - -|Item|Value| -|-|-| -|Category|Publish| -|Enabled|True| -|Severity|Warning| -|CodeFix|False| ---- diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 3f418be3a1..9646ae0112 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -5014,46 +5014,6 @@ "EnabledRuleInAggressiveMode" ] } - }, - "IL3000": { - "id": "IL3000", - "shortDescription": "Avoid using accessing Assembly file path when publishing as a single-file", - "fullDescription": "'{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'.", - "defaultLevel": "warning", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3000", - "properties": { - "category": "Publish", - "isEnabledByDefault": true, - "typeName": "AvoidAssemblyLocationInSingleFile", - "languages": [ - "C#", - "Visual Basic" - ], - "tags": [ - "Telemetry", - "EnabledRuleInAggressiveMode" - ] - } - }, - "IL3001": { - "id": "IL3001", - "shortDescription": "Avoid using accessing Assembly file path when publishing as a single-file", - "fullDescription": "'{0}' will throw for assemblies embedded in a single-file app", - "defaultLevel": "warning", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3001", - "properties": { - "category": "Publish", - "isEnabledByDefault": true, - "typeName": "AvoidAssemblyLocationInSingleFile", - "languages": [ - "C#", - "Visual Basic" - ], - "tags": [ - "Telemetry", - "EnabledRuleInAggressiveMode" - ] - } } } }, diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Publish/AvoidAssemblyLocationInSingleFileTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Publish/AvoidAssemblyLocationInSingleFileTests.cs deleted file mode 100644 index 780cb653cc..0000000000 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Publish/AvoidAssemblyLocationInSingleFileTests.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Testing; -using Xunit; -using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.NetCore.Analyzers.Publish.AvoidAssemblyLocationInSingleFile, - Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; -using static Analyzer.Utilities.MSBuildPropertyOptionNames; -using static Microsoft.NetCore.Analyzers.Publish.AvoidAssemblyLocationInSingleFile; - -namespace Microsoft.NetCore.Analyzers.Publish.UnitTests -{ - public class AvoidAssemblyLocationInSingleFileTests - { - [Theory] - [CombinatorialData] - public Task GetExecutingAssemblyLocation( - [CombinatorialValues(true, false, null)] bool? publish, - [CombinatorialValues(true, false, null)] bool? includeContent) - { - const string source = @" -using System.Reflection; -class C -{ - public string M() => Assembly.GetExecutingAssembly().Location; -}"; - string analyzerConfig = ""; - if (publish is not null) - { - analyzerConfig += $"build_property.{PublishSingleFile} = {publish}" + Environment.NewLine; - } - if (includeContent is not null) - { - analyzerConfig += $"build_property.{IncludeAllContentForSelfExtract} = {includeContent}"; - } - - var test = new VerifyCS.Test - { - TestCode = source, - AnalyzerConfigDocument = analyzerConfig - }; - - DiagnosticResult[] diagnostics; - if (publish is true && includeContent is not true) - { - diagnostics = new DiagnosticResult[] { - // /0/Test0.cs(5,26): warning IL3000: 'System.Reflection.Assembly.Location' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - VerifyCS.Diagnostic(AvoidAssemblyLocationInSingleFile.IL3000).WithSpan(5, 26, 5, 66).WithArguments("System.Reflection.Assembly.Location"), - }; - } - else - { - diagnostics = Array.Empty(); - } - - test.ExpectedDiagnostics.AddRange(diagnostics); - return test.RunAsync(); - } - - [Fact] - public Task AssemblyProperties() - { - var src = @" -using System.Reflection; -class C -{ - public void M() - { - var a = Assembly.GetExecutingAssembly(); - _ = a.Location; - // below will be obsolete in 5.0 - _ = a.CodeBase; - _ = a.EscapedCodeBase; - } -}"; - return VerifyDiagnosticsAsync(src, - // /0/Test0.cs(8,13): warning IL3000: 'System.Reflection.Assembly.Location' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - VerifyCS.Diagnostic(AvoidAssemblyLocationInSingleFile.IL3000).WithSpan(8, 13, 8, 23).WithArguments("System.Reflection.Assembly.Location") - ); - } - - [Fact] - public Task AssemblyMethods() - { - var src = @" -using System.Reflection; -class C -{ - public void M() - { - var a = Assembly.GetExecutingAssembly(); - _ = a.GetFile(""/some/file/path""); - _ = a.GetFiles(); - } -}"; - return VerifyDiagnosticsAsync(src, - // /0/Test0.cs(8,13): warning IL3001: Assemblies embedded in a single-file app cannot have additional files in the manifest. - VerifyCS.Diagnostic(AvoidAssemblyLocationInSingleFile.IL3001).WithSpan(8, 13, 8, 41).WithArguments("System.Reflection.Assembly.GetFile(string)"), - // /0/Test0.cs(9,13): warning IL3001: Assemblies embedded in a single-file app cannot have additional files in the manifest. - VerifyCS.Diagnostic(AvoidAssemblyLocationInSingleFile.IL3001).WithSpan(9, 13, 9, 25).WithArguments("System.Reflection.Assembly.GetFiles()") - ); - } - - [Fact] - public Task AssemblyNameAttributes() - { - var src = @" -using System.Reflection; -class C -{ - public void M() - { - var a = Assembly.GetExecutingAssembly().GetName(); - _ = a.CodeBase; - _ = a.EscapedCodeBase; - } -}"; - return VerifyDiagnosticsAsync(src, - // /0/Test0.cs(8,13): warning IL3000: 'System.Reflection.AssemblyName.CodeBase' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - VerifyCS.Diagnostic(AvoidAssemblyLocationInSingleFile.IL3000).WithSpan(8, 13, 8, 23).WithArguments("System.Reflection.AssemblyName.CodeBase"), - // /0/Test0.cs(9,13): warning IL3000: 'System.Reflection.AssemblyName.EscapedCodeBase' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - VerifyCS.Diagnostic(AvoidAssemblyLocationInSingleFile.IL3000).WithSpan(9, 13, 9, 30).WithArguments("System.Reflection.AssemblyName.EscapedCodeBase") - ); - } - - [Fact] - public Task FalsePositive() - { - // This is an OK use of Location and GetFile since these assemblies were loaded from - // a file, but the analyzer is conservative - var src = @" -using System.Reflection; -class C -{ - public void M() - { - var a = Assembly.LoadFrom(""/some/path/not/in/bundle""); - _ = a.Location; - _ = a.GetFiles(); - } -}"; - return VerifyDiagnosticsAsync(src, - // /0/Test0.cs(8,13): warning IL3000: 'System.Reflection.Assembly.Location' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. - VerifyCS.Diagnostic(AvoidAssemblyLocationInSingleFile.IL3000).WithSpan(8, 13, 8, 23).WithArguments("System.Reflection.Assembly.Location"), - // /0/Test0.cs(9,13): warning IL3001: Assemblies embedded in a single-file app cannot have additional files in the manifest. - VerifyCS.Diagnostic(AvoidAssemblyLocationInSingleFile.IL3001).WithSpan(9, 13, 9, 25).WithArguments("System.Reflection.Assembly.GetFiles()") - ); - } - - private Task VerifyDiagnosticsAsync(string source, params DiagnosticResult[] expected) - { - const string singleFilePublishConfig = @" -build_property." + PublishSingleFile + " = true"; - - var test = new VerifyCS.Test - { - TestCode = source, - AnalyzerConfigDocument = singleFilePublishConfig - }; - - test.ExpectedDiagnostics.AddRange(expected); - return test.RunAsync(); - } - } -} \ No newline at end of file diff --git a/src/Utilities/Compiler/DiagnosticCategory.cs b/src/Utilities/Compiler/DiagnosticCategory.cs index 69def675b9..3b32ebccb3 100644 --- a/src/Utilities/Compiler/DiagnosticCategory.cs +++ b/src/Utilities/Compiler/DiagnosticCategory.cs @@ -16,7 +16,6 @@ internal static class DiagnosticCategory public const string Library = nameof(Library); public const string Documentation = nameof(Documentation); public const string Maintainability = nameof(Maintainability); - public const string Publish = nameof(Publish); public const string RoslynDiagnosticsDesign = nameof(RoslynDiagnosticsDesign); public const string RoslynDiagnosticsMaintainability = nameof(RoslynDiagnosticsMaintainability); diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 793ed1c657..94d4940230 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -35,8 +35,4 @@ RoslynDiagnosticsDesign: RS0000-RS0999 RoslynDiagnosticsMaintainability: RS0000-RS0999 RoslynDiagnosticsPerformance: RS0000-RS0999 RoslynDiagnosticsReliability: RS0000-RS0999 -RoslynDiagnosticsUsage: RS0000-RS0999 - -# dotnet publish rules -# These are warnings for single-file publish or the IL trimmer -Publish: IL0000-IL9999 \ No newline at end of file +RoslynDiagnosticsUsage: RS0000-RS0999 \ No newline at end of file diff --git a/src/Utilities/Compiler/Options/MSBuildPropertyOptionNames.cs b/src/Utilities/Compiler/Options/MSBuildPropertyOptionNames.cs index 891cd4de94..5e4940793b 100644 --- a/src/Utilities/Compiler/Options/MSBuildPropertyOptionNames.cs +++ b/src/Utilities/Compiler/Options/MSBuildPropertyOptionNames.cs @@ -17,8 +17,6 @@ internal static class MSBuildPropertyOptionNames public const string TargetPlatformMinVersion = nameof(TargetPlatformMinVersion); public const string UsingMicrosoftNETSdkWeb = nameof(UsingMicrosoftNETSdkWeb); public const string ProjectTypeGuids = nameof(ProjectTypeGuids); - public const string PublishSingleFile = nameof(PublishSingleFile); - public const string IncludeAllContentForSelfExtract = nameof(IncludeAllContentForSelfExtract); } internal static class MSBuildPropertyOptionNamesHelpers From 38ff0f063a1c27efc209b276f428b1b9bb22ef17 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Mon, 11 Jan 2021 02:58:59 -0500 Subject: [PATCH 006/224] Added PreferDictionaryContainsMethods Analyzer - RuleId: CA1839 --- .../Core/AnalyzerReleases.Unshipped.md | 5 +- .../MicrosoftNetCoreAnalyzersResources.resx | 21 + .../PreferDictionaryContainsMethods.Fixer.cs | 71 +++ .../PreferDictionaryContainsMethods.cs | 178 +++++++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.de.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.es.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.it.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 35 ++ ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 35 ++ .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 35 ++ ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 35 ++ ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 35 ++ .../PreferDictionaryContainsMethodsTests.cs | 443 ++++++++++++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- src/Utilities/Compiler/WellKnownTypeNames.cs | 1 + 20 files changed, 1174 insertions(+), 2 deletions(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethodsTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 0058139817..3b90729293 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1 +1,4 @@ -; Please do not edit this file manually, it should only be updated through code fix application. \ No newline at end of file +; Please do not edit this file manually, it should only be updated through code fix application.### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +CA1839 | Performance | Info | PreferDictionaryContainsMethods, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839) \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index e240378b84..fc07f12d38 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1519,4 +1519,25 @@ and all other platforms This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms + + Use 'ContainsKey(TKey)' + + + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + Prefer Dictionary Contains Methods + + + Use 'ContainsValue(TValue)' + + + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs new file mode 100644 index 0000000000..6486ffeeb2 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Editing; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public class PreferDictionaryContainsMethodsFixer : CodeFixProvider + { + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Document doc = context.Document; + SyntaxNode root = await doc.GetSyntaxRootAsync().ConfigureAwait(false); + SyntaxNode invocation = root.FindNode(context.Span); + SyntaxNode containsMemberAccess = invocation.ChildNodes().First(); + SyntaxNode argumentList = invocation.ChildNodes().ElementAt(1); + SyntaxNode keysOrValuesMemberAccess = containsMemberAccess.ChildNodes().First(); + SyntaxNode receiver = keysOrValuesMemberAccess.ChildNodes().First(); + SyntaxNode propertyIdentifier = keysOrValuesMemberAccess.ChildNodes().ElementAt(1); + + if (propertyIdentifier.GetText().ToString() == PreferDictionaryContainsMethods.KeysPropertyName) + { + var action = CodeAction.Create( + MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsKeyCodeFixTitle, + ReplaceKeysContainsWithContainsKeyAsync, + MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsKeyCodeFixTitle); + context.RegisterCodeFix(action, context.Diagnostics); + } + else if (propertyIdentifier.GetText().ToString() == PreferDictionaryContainsMethods.ValuesPropertyName) + { + var action = CodeAction.Create( + MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueCodeFixTitle, + ReplaceValuesContainsWithContainsValueAsync, + MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueCodeFixTitle); + context.RegisterCodeFix(action, context.Diagnostics); + } + + async Task ReplaceKeysContainsWithContainsKeyAsync(CancellationToken ct) + { + var editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); + var containsKeyMemberAccess = editor.Generator.MemberAccessExpression(receiver, PreferDictionaryContainsMethods.ContainsKeyMethodName); + var newInvocation = editor.Generator.InvocationExpression(containsKeyMemberAccess, argumentList.ChildNodes()); + editor.ReplaceNode(invocation, newInvocation); + + return editor.GetChangedDocument(); + } + + async Task ReplaceValuesContainsWithContainsValueAsync(CancellationToken ct) + { + var editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); + var containsValueMemberAccess = editor.Generator.MemberAccessExpression(receiver, PreferDictionaryContainsMethods.ContainsValueMethodName); + var newInvocation = editor.Generator.InvocationExpression(containsValueMemberAccess, argumentList.ChildNodes()); + editor.ReplaceNode(invocation, newInvocation); + + return editor.GetChangedDocument(); + } + } + + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(PreferDictionaryContainsMethods.RuleId); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs new file mode 100644 index 0000000000..0ba0300359 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class PreferDictionaryContainsMethods : DiagnosticAnalyzer + { + internal const string RuleId = "CA1839"; + + private static readonly LocalizableString s_localizableTitle = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsMethodsTitle)); + private static readonly LocalizableString s_localizableContainsKeyMessage = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsKeyMessage)); + private static readonly LocalizableString s_localizableContainsKeyDescription = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsKeyDescription)); + private static readonly LocalizableString s_localizableContainsValueMessage = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueMessage)); + private static readonly LocalizableString s_localizableContainsValueDescription = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueDescription)); + + internal static readonly DiagnosticDescriptor ContainsKeyRule = DiagnosticDescriptorHelper.Create( + RuleId, + s_localizableTitle, + s_localizableContainsKeyMessage, + DiagnosticCategory.Performance, + RuleLevel.BuildWarningCandidate, + s_localizableContainsKeyDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor ContainsValueRule = DiagnosticDescriptorHelper.Create( + RuleId, + s_localizableTitle, + s_localizableContainsValueMessage, + DiagnosticCategory.Performance, + RuleLevel.BuildWarningCandidate, + s_localizableContainsValueDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + private const string ContainsMethodName = "Contains"; + internal const string ContainsKeyMethodName = "ContainsKey"; + internal const string ContainsValueMethodName = "ContainsValue"; + internal const string KeysPropertyName = "Keys"; + internal const string ValuesPropertyName = "Values"; + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(ContainsKeyRule, ContainsValueRule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext compilationContext) + { + var compilation = compilationContext.Compilation; + + if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericICollection1, out var icollectionType)) + return; + if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericIDictionary2, out var idictionaryType)) + return; + if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericIEnumerable1, out var ienumerableType)) + return; + + compilationContext.RegisterOperationAction(OnOperationAction, OperationKind.Invocation); + + void OnOperationAction(OperationAnalysisContext context) + { + var invocation = (IInvocationOperation)context.Operation; + IMethodSymbol containsMethod = invocation.TargetMethod; + + if (containsMethod.Name != ContainsMethodName) + return; + if (containsMethod.ReturnType.SpecialType != SpecialType.System_Boolean) + return; + if (!TryGetPropertyReferenceOperation(invocation, out IPropertySymbol? property)) + return; + if (!DoesContainsCandidateHaveCorrectArgumentCount(containsMethod)) + return; + if (!TryGetConstructedDictionaryType(property.ContainingType, out INamedTypeSymbol? constructedDictionaryType)) + return; + + ITypeSymbol keyType = constructedDictionaryType.TypeArguments[0]; + ITypeSymbol valueType = constructedDictionaryType.TypeArguments[1]; + ITypeSymbol containsParameterType = containsMethod.Parameters.Last().Type; + + if (property.Name == KeysPropertyName) + { + if (!containsParameterType.Equals(keyType, SymbolEqualityComparer.Default)) + return; + + IMethodSymbol? containsKeyMethod = property.ContainingType.GetMembers(ContainsKeyMethodName) + .OfType() + .WhereAsArray(x => x.ReturnType.SpecialType == SpecialType.System_Boolean && + x.IsPublic() && + x.Parameters.Length == 1 && + x.Parameters[0].Type.Equals(keyType, SymbolEqualityComparer.Default)) + .SingleOrDefault(); + + if (containsKeyMethod is null) + return; + + var diagnostic = Diagnostic.Create(ContainsKeyRule, invocation.Syntax.GetLocation(), property.ContainingType.Name); + context.ReportDiagnostic(diagnostic); + } + else if (property.Name == ValuesPropertyName) + { + if (!containsParameterType.Equals(valueType, SymbolEqualityComparer.Default)) + return; + + IMethodSymbol? containsValueMethod = property.ContainingType.GetMembers(ContainsValueMethodName) + .OfType() + .WhereAsArray(x => x.ReturnType.SpecialType == SpecialType.System_Boolean && + x.Parameters.Length == 1 && + x.Parameters[0].Type.Equals(valueType, SymbolEqualityComparer.Default)) + .SingleOrDefault(); + + if (containsValueMethod is null) + return; + + var diagnostic = Diagnostic.Create(ContainsValueRule, invocation.Syntax.GetLocation(), property.ContainingType.Name); + context.ReportDiagnostic(diagnostic); + } + } + + static bool TryGetPropertyReferenceOperation(IInvocationOperation containsInvocation, [NotNullWhen(true)] out IPropertySymbol? property) + { + IOperation current = containsInvocation; + + if (containsInvocation.TargetMethod.IsExtensionMethod) + { + if (current.Children.FirstOrDefault() is IArgumentOperation argumentOperation) + current = argumentOperation; + if (current.Children.FirstOrDefault() is IConversionOperation conversionOperation) + current = conversionOperation; + } + + property = (current.Children.FirstOrDefault() as IPropertyReferenceOperation)?.Property; + return property is not null; + } + + bool TryGetConstructedDictionaryType(INamedTypeSymbol derived, [NotNullWhen(true)] out INamedTypeSymbol? constructedDictionaryType) + { + constructedDictionaryType = derived.GetBaseTypesAndThis() + .WhereAsArray(x => x.OriginalDefinition.Equals(idictionaryType, SymbolEqualityComparer.Default)) + .SingleOrDefault(); + + if (constructedDictionaryType is null) + { + constructedDictionaryType = derived.AllInterfaces + .WhereAsArray(x => x.OriginalDefinition.Equals(idictionaryType, SymbolEqualityComparer.Default)) + .SingleOrDefault(); + } + + return constructedDictionaryType is not null; + } + + static bool DoesContainsCandidateHaveCorrectArgumentCount(IMethodSymbol containsCandidate) + { + if (containsCandidate.Language == LanguageNames.CSharp && containsCandidate.IsExtensionMethod) + return containsCandidate.Parameters.Length == 2; + else + return containsCandidate.Parameters.Length == 1; + } + } + + private static LocalizableString CreateResource(string resourceName) + { + return new LocalizableResourceString(resourceName, MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index be063a9c1a..36d4bd69e9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -1602,6 +1602,41 @@ Potenciální cyklus odkazů v deserializovaném grafu objektů + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Stream má přetížení ReadAsync, které jako první argument přijímá Memory<Byte>, a přetížení WriteAsync, které jako první argument přijímá ReadOnlyMemory<Byte>. Upřednostňujte volání přetížení založených na paměti, která jsou efektivnější. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 52ca3a058f..d02260ee46 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -1602,6 +1602,41 @@ Potenzieller Verweiszyklus in deserialisiertem Objektgraph + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Stream verfügt über eine Überladung "ReadAsync", die "Memory<Byte>" als erstes Argument akzeptiert, sowie über eine Überladung "WriteAsync", die "ReadOnlyMemory<Byte>" als erstes Argument akzeptiert. Rufen Sie möglichst arbeitsspeicherbasierte Überladungen auf, da diese effizienter sind. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 18668ee783..11c4287b08 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -1602,6 +1602,41 @@ Posible ciclo de referencia en el gráfico de objetos deserializados + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" tiene una sobrecarga "ReadAsync" que toma "Memory<Byte>" como primer argumento y una sobrecarga "WriteAsync" que toma "ReadOnlyMemory<Byte>" como primer argumento. Es preferible llamar a las sobrecargas basadas en memory, que son más eficaces. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index d90f9ec3af..4035e68aa2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -1602,6 +1602,41 @@ Cycle de référence potentiel dans un graphe d'objet désérialisé + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream’ a une surcharge ’ReadAsync’ qui prend un ’Memory<Byte>' comme premier argument et une surcharge ’WriteAsync’ qui prend un ’ReadOnlyMemory<Byte>' comme premier argument. Préférez l'appel des surcharges basées sur la mémoire, car elles sont plus efficaces. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 09c16a9f6e..b69eb0dc07 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -1602,6 +1602,41 @@ Potenziale ciclo di riferimento nel grafico di oggetti deserializzati + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' contiene un overload 'ReadAsync' che accetta 'Memory<Byte>' come primo argomento e un overload 'WriteAsync' che accetta 'ReadOnlyMemory<Byte>' come primo argomento. Per la chiamata preferire gli overload basati su Memory, che sono più efficaci. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 6937c685a2..09113b9107 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -1602,6 +1602,41 @@ 逆シリアル化されたオブジェクト グラフ内の参照サイクルの可能性 + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' には、最初の引数として 'Memory<Byte>' を取る 'ReadAsync' オーバーロードと、最初の引数として 'ReadOnlyMemory<Byte>' を取る 'WriteAsync' オーバーロードがあります。より効率的なメモリ ベースのオーバーロードを呼び出すことをお勧めします。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index d329428df3..de08e31d31 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -1602,6 +1602,41 @@ 역직렬화된 개체 그래프의 잠재적 참조 주기 + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream'에 첫 번째 인수로 'Memory<Byte>'를 사용하는 'ReadAsync' 오버로드와 첫 번째 인수로 'ReadOnlyMemory<Byte>'를 사용하는 'WriteAsync' 오버로드가 있습니다. 더 효율적인 메모리 기반 오버로드를 호출하는 것이 좋습니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 0131fbd52a..17790a1267 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -1602,6 +1602,41 @@ Potencjalny cykl odwołań w deserializowanym grafie obiektów + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Element „Stream” ma przeciążenie „ReadAsync”, które przyjmuje „Memory<Byte>” jako pierwszy argument i przeciążenie „WriteAsync”, które przyjmuje „ReadOnlyMemory<Byte>” jako pierwszy argument. Preferuj wywoływanie przeciążeń opartych na pamięci, co jest bardziej wydajne. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 9e63b6fc06..9b6150b68d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -1602,6 +1602,41 @@ Ciclo de referência potencial em grafo de objeto desserializado + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' tem uma sobrecarga 'ReadAsync' que recebe um 'Memory<Byte>' como o primeiro argumento e uma sobrecarga 'WriteAsync' que recebe um 'ReadOnlyMemory<Byte>' como o primeiro argumento. Prefira chamar as sobrecargas com base na memória, que são mais eficientes. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 6dbafe9c23..07c4fdd87c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -1602,6 +1602,41 @@ Потенциальное зацикливание ссылок в графе десериализованного объекта + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" содержит перегрузку "ReadAsync", которая принимает в качестве первого аргумента "Memory<Byte>", и перегрузку "WriteAsync", которая принимает в качестве первого аргумента "ReadOnlyMemory<Byte>". Старайтесь использовать перегрузки на основе памяти, которые являются более эффективными. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index c4dd09e3bd..088fcb592c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -1602,6 +1602,41 @@ Seri durumdan çıkarılan nesne grafındaki olası başvuru döngüsü + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream', ilk bağımsız değişken olarak 'Memory<Byte>' alan bir 'ReadAsync' aşırı yüklemesine ve ilk bağımsız değişken olarak 'ReadOnlyMemory<Byte>' alan bir 'WriteAsync' aşırı yüklemesine sahip. Daha verimli olan bellek tabanlı aşırı yüklemeleri çağırmayı tercih edin. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 1a161cab86..994a1f0f2c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -1602,6 +1602,41 @@ 反序列化对象图中的潜在引用循环 + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" 有一个将 "Memory<Byte>" 作为第一个参数的 "ReadAsync" 重载和一个将 "Memory<Byte>" 作为第一个参数的 "WriteAsync" 重载。首选调用基于内存的重载,它们的效率更高。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 7bd5957db2..65a18c1ba3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -1602,6 +1602,41 @@ 還原序列化物件圖中的潛在參考迴圈 + + Use 'ContainsKey(TKey)' + Use 'ContainsKey(TKey)' + + + + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + + + + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + Prefer '{0}.ContainsKey(TKey)' over '{0}.Keys.Contains(TKey)' + + + + Prefer Dictionary Contains Methods + Prefer Dictionary Contains Methods + + + + Use 'ContainsValue(TValue)' + Use 'ContainsValue(TValue)' + + + + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + + + + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + Prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' 具有採用 'Memory<Byte>' 作為第一個引數的 'ReadAsync' 多載,以及採用 'ReadOnlyMemory<Byte>' 作為第一個引數的 'WriteAsync' 多載。建議呼叫採用記憶體的多載,較有效率。 diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethodsTests.cs new file mode 100644 index 0000000000..5c1fb224c0 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethodsTests.cs @@ -0,0 +1,443 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Test.Utilities; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.PreferDictionaryContainsMethods, + Microsoft.NetCore.Analyzers.Runtime.PreferDictionaryContainsMethodsFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.PreferDictionaryContainsMethods, + Microsoft.NetCore.Analyzers.Runtime.PreferDictionaryContainsMethodsFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class PreferDictionaryContainsMethodsTests + { + #region Expected Diagnostic + [Fact] + public async Task IDictionary_Keys_Contains_ReportsDiagnostic_CS() + { + const string declaration = @"IDictionary dictionary = new Dictionary();"; + string testCode = CreateCSSource(declaration, @"bool wutzHuh = {|#0:dictionary.Keys.Contains(""Wumphed"")|};"); + string fixedCode = CreateCSSource(declaration, @"bool wutzHuh = dictionary.ContainsKey(""Wumphed"");"); + var diagnostic = VerifyCS.Diagnostic(ContainsKeyRule) + .WithLocation(0) + .WithArguments("IDictionary"); + + await new VerifyCS.Test + { + TestCode = testCode, + FixedCode = fixedCode, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task IDictionary_Keys_Contains_ReportsDiagnostic_VB() + { + const string declaration = @"Dim dictionary As IDictionary(Of String, Integer) = New Dictionary(Of String, Integer)()"; + string testCode = CreateVBSource(declaration, @"Dim wutzHuh = {|#0:dictionary.Keys.Contains(""Wumphed"")|}"); + string fixedCode = CreateVBSource(declaration, @"Dim wutzHuh = dictionary.ContainsKey(""Wumphed"")"); + var diagnostic = VerifyVB.Diagnostic(ContainsKeyRule) + .WithLocation(0) + .WithArguments("IDictionary"); + + await new VerifyVB.Test + { + TestCode = testCode, + FixedCode = fixedCode, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task BuiltInDictionary_Keys_Contains_ReportsDiagnostic_CS() + { + const string declaration = @"var dictionary = new Dictionary();"; + string testCode = CreateCSSource(declaration, @"bool isPhleeb = {|#0:dictionary.Keys.Contains(""bizzurf"")|};"); + string fixedCode = CreateCSSource(declaration, @"bool isPhleeb = dictionary.ContainsKey(""bizzurf"");"); + var diagnostic = VerifyCS.Diagnostic(ContainsKeyRule) + .WithLocation(0) + .WithArguments("Dictionary"); + + await new VerifyCS.Test + { + TestCode = testCode, + FixedCode = fixedCode, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task BuiltInDictionary_Keys_Contains_ReportsDiagnostic_VB() + { + const string declaration = "Dim dictionary = New Dictionary(Of String, Integer)()"; + string testCode = CreateVBSource(declaration, @"Dim isPhleeb = {|#0:dictionary.Keys.Contains(""bizzurf"")|}"); + string fixedCode = CreateVBSource(declaration, @"Dim isPhleeb = dictionary.ContainsKey(""bizzurf"")"); + var diagnostic = VerifyVB.Diagnostic(ContainsKeyRule) + .WithLocation(0) + .WithArguments("Dictionary"); + + await new VerifyVB.Test + { + TestCode = testCode, + FixedCode = fixedCode, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task BuiltInDictionary_Values_Contains_ReportsDiagnostic_CS() + { + const string declaration = "var dictionary = new Dictionary();"; + string testCode = CreateCSSource(declaration, @"bool wasFlobbed = {|#0:dictionary.Values.Contains(75)|};"); + string fixedCode = CreateCSSource(declaration, @"bool wasFlobbed = dictionary.ContainsValue(75);"); + var diagnostic = VerifyCS.Diagnostic(ContainsValueRule) + .WithLocation(0) + .WithArguments("Dictionary"); + + await new VerifyCS.Test + { + TestCode = testCode, + FixedCode = fixedCode, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task BuiltInDictionary_Values_Contains_ReportsDiagnostic_VB() + { + const string declaration = "Dim dictionary = New Dictionary(Of String, Integer)()"; + string testCode = CreateVBSource(declaration, @"Dim wasFlobbed = {|#0:dictionary.Values.Contains(75)|}"); + string fixedCode = CreateVBSource(declaration, @"Dim wasFlobbed = dictionary.ContainsValue(75)"); + var diagnostic = VerifyVB.Diagnostic(ContainsValueRule) + .WithLocation(0) + .WithArguments("Dictionary"); + + await new VerifyVB.Test + { + TestCode = testCode, + FixedCode = fixedCode, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task ExplicitContainsKey_WhenTypedAsIDictionary_ReportsDiagnostic_CS() + { + const string declaration = "IDictionary dictionary = new TestDictionary();"; + string testCode = CreateCSSource(declaration, @"bool tugBlug = {|#0:dictionary.Keys.Contains(""bricklebrit"")|};"); + string fixedCode = CreateCSSource(declaration, @"bool tugBlug = dictionary.ContainsKey(""bricklebrit"");"); + var diagnostic = VerifyCS.Diagnostic(ContainsKeyRule) + .WithLocation(0) + .WithArguments("IDictionary"); + + await new VerifyCS.Test + { + TestState = { Sources = { testCode, CSExplicitContainsKeyDictionarySource } }, + FixedState = { Sources = { fixedCode, CSExplicitContainsKeyDictionarySource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task ExplicitContainsKey_WhenTypedAsIDictionary_ReportsDiagnostic_VB() + { + const string declaration = "Dim dictionary As IDictionary(Of String, Integer) = New TestDictionary()"; + string testCode = CreateVBSource(declaration, @"Dim tugBlug = {|#0:dictionary.Keys.Contains(""bricklebrit"")|}"); + string fixedCode = CreateVBSource(declaration, @"Dim tugBlug = dictionary.ContainsKey(""bricklebrit"")"); + var diagnostic = VerifyVB.Diagnostic(ContainsKeyRule) + .WithLocation(0) + .WithArguments("IDictionary"); + + await new VerifyVB.Test + { + TestState = { Sources = { testCode, VBExplicitContainsKeyDictionarySource } }, + FixedState = { Sources = { fixedCode, VBExplicitContainsKeyDictionarySource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + #endregion + + #region No Diagnostic + [Fact] + public async Task IDictionary_Values_Contains_NoDiagnostic_CS() + { + const string declaration = @"IDictionary dictionary = new Dictionary();"; + string testCode = CreateCSSource(declaration, @"bool urrp = dictionary.Values.Contains(49);"); + + await new VerifyCS.Test + { + TestCode = testCode, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + + [Fact] + public async Task IDictionary_Values_Contains_NoDiagnostic_VB() + { + const string declaration = @"Dim dictionary As IDictionary(Of String, Integer) = New Dictionary(Of String, Integer)()"; + string testCode = CreateVBSource(declaration, @"Dim urrp = dictionary.Values.Contains(49)"); + + await new VerifyVB.Test + { + TestCode = testCode, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + + [Fact] + public async Task ExplicitContainsKey_Keys_Contains_NoDiagnostic_CS() + { + string testCode = CreateCSSource( + @"var dictionary = new TestDictionary();", + @"bool buzzed = dictionary.Keys.Contains(""moofled"");"); + + await new VerifyCS.Test + { + TestState = { Sources = { testCode, CSExplicitContainsKeyDictionarySource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + + [Fact] + public async Task ExplicitContainsKey_Keys_Contains_NoDiagnostic_VB() + { + string testCode = CreateVBSource( + @"Dim dictionary = New TestDictionary()", + @"Dim buzzed = dictionary.Keys.Contains(""moofled"")"); + + await new VerifyVB.Test + { + TestState = { Sources = { testCode, VBExplicitContainsKeyDictionarySource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + #endregion + + #region Helpers + private static string CreateCSSource(params string[] statements) + { + string body = @" +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Testopolis +{{ + public class WumphingWumbler + {{ + public void MegaWumph() + {{ + {0} + }} + }} +}} +"; + return string.Format(System.Globalization.CultureInfo.InvariantCulture, body, string.Join("\r\n ", statements)); + } + + private static string CreateVBSource(params string[] statements) + { + string body = @" +Imports System +Imports System.Collections.Generic +Imports System.Linq + +Namespace Testopolis + + Public Class WumphingWumbler + + Public Sub MegaWumph() + {0} + End Sub + End Class +End Namespace +"; + return string.Format(System.Globalization.CultureInfo.InvariantCulture, body, string.Join("\r\n ", statements)); + } + + private const string CSExplicitContainsKeyDictionarySource = @" +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Testopolis +{ + public class TestDictionary : IDictionary + { + bool IDictionary.ContainsKey(string key) + { + throw new NotImplementedException(); + } + + public void Add(string key, int value) + { + throw new NotImplementedException(); + } + + public bool Remove(string key) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(string key, out int value) + { + throw new NotImplementedException(); + } + + public int this[string key] + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public ICollection Keys { get; } + public ICollection Values { get; } + + public void Add(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public bool Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public bool Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public int Count { get; } + public bool IsReadOnly { get; } + + public IEnumerator> GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } +} +"; + + private const string VBExplicitContainsKeyDictionarySource = @" +Imports System +Imports System.Collections +Imports System.Collections.Generic +Imports System.Linq + +Namespace Testopolis + Public Class TestDictionary : Implements IDictionary(Of String, Integer) + + Private Function ContainsKey(key As String) As Boolean Implements IDictionary(Of String, Integer).ContainsKey + Throw New NotImplementedException() + End Function + + Default Public Property Item(key As String) As Integer Implements IDictionary(Of String, Integer).Item + Get + Throw New NotImplementedException() + End Get + Set(value As Integer) + Throw New NotImplementedException() + End Set + End Property + + Public ReadOnly Property Keys As ICollection(Of String) Implements IDictionary(Of String, Integer).Keys + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property Values As ICollection(Of Integer) Implements IDictionary(Of String, Integer).Values + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property Count As Integer Implements ICollection(Of KeyValuePair(Of String, Integer)).Count + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of KeyValuePair(Of String, Integer)).IsReadOnly + Get + Throw New NotImplementedException() + End Get + End Property + + Public Sub Add(key As String, value As Integer) Implements IDictionary(Of String, Integer).Add + Throw New NotImplementedException() + End Sub + + Public Sub Add(item As KeyValuePair(Of String, Integer)) Implements ICollection(Of KeyValuePair(Of String, Integer)).Add + Throw New NotImplementedException() + End Sub + + Public Sub Clear() Implements ICollection(Of KeyValuePair(Of String, Integer)).Clear + Throw New NotImplementedException() + End Sub + + Public Sub CopyTo(array() As KeyValuePair(Of String, Integer), arrayIndex As Integer) Implements ICollection(Of KeyValuePair(Of String, Integer)).CopyTo + Throw New NotImplementedException() + End Sub + + Public Function Remove(key As String) As Boolean Implements IDictionary(Of String, Integer).Remove + Throw New NotImplementedException() + End Function + + Public Function Remove(item As KeyValuePair(Of String, Integer)) As Boolean Implements ICollection(Of KeyValuePair(Of String, Integer)).Remove + Throw New NotImplementedException() + End Function + + Public Function TryGetValue(key As String, ByRef value As Integer) As Boolean Implements IDictionary(Of String, Integer).TryGetValue + Throw New NotImplementedException() + End Function + + Public Function Contains(item As KeyValuePair(Of String, Integer)) As Boolean Implements ICollection(Of KeyValuePair(Of String, Integer)).Contains + Throw New NotImplementedException() + End Function + + Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of String, Integer)) Implements IEnumerable(Of KeyValuePair(Of String, Integer)).GetEnumerator + Throw New NotImplementedException() + End Function + + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class +End Namespace"; + + private static DiagnosticDescriptor ContainsKeyRule => PreferDictionaryContainsMethods.ContainsKeyRule; + private static DiagnosticDescriptor ContainsValueRule => PreferDictionaryContainsMethods.ContainsValueRule; + #endregion + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 793ed1c657..c055844a38 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1310 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1838 +Performance: HA, CA1800-CA1839 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5403 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2249 Naming: CA1700-CA1726 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 2a45fad59c..3adeb56f0d 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -107,6 +107,7 @@ internal static class WellKnownTypeNames public const string SystemCollectionsGenericDictionary2 = "System.Collections.Generic.Dictionary`2"; public const string SystemCollectionsGenericHashSet1 = "System.Collections.Generic.HashSet`1"; public const string SystemCollectionsGenericICollection1 = "System.Collections.Generic.ICollection`1"; + public const string SystemCollectionsGenericIDictionary2 = "System.Collections.Generic.IDictionary`2"; public const string SystemCollectionsGenericIEnumerable1 = "System.Collections.Generic.IEnumerable`1"; public const string SystemCollectionsGenericIEnumerator1 = "System.Collections.Generic.IEnumerator`1"; public const string SystemCollectionsGenericIEqualityComparer1 = "System.Collections.Generic.IEqualityComparer`1"; From 89f58a2361a8252f93b2bbbbc8f0e033e311b136 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 11 Jan 2021 09:46:14 -0800 Subject: [PATCH 007/224] Update src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md --- src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 332cd78356..64372afc0b 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1,6 +1,7 @@ ; Please do not edit this file manually, it should only be updated through code fix application. ### Removed Rules + Rule ID | Category | Severity | Notes --------|----------|----------|------- CA1801 | Usage | Disabled | ReviewUnusedParametersAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1801) From 7e83b79de9219d454b3e5df6062df2a6f6c05afc Mon Sep 17 00:00:00 2001 From: NewellClark Date: Mon, 11 Jan 2021 23:47:41 -0500 Subject: [PATCH 008/224] Refactored fixer into separate CS and VB implementations --- ...arpPreferDictionaryContainsMethodsFixer.cs | 72 ++ .../Core/AnalyzerReleases.Unshipped.md | 5 +- .../PreferDictionaryContainsMethods.Fixer.cs | 53 +- .../PreferDictionaryContainsMethods.cs | 6 +- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 8 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 8 +- .../PreferDictionaryContainsMethodsTests.cs | 644 +++++++++++++++++- ...sicPreferDictionaryContainsMethodsFixer.vb | 61 ++ 19 files changed, 826 insertions(+), 119 deletions(-) create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpPreferDictionaryContainsMethodsFixer.cs create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicPreferDictionaryContainsMethodsFixer.vb diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpPreferDictionaryContainsMethodsFixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpPreferDictionaryContainsMethodsFixer.cs new file mode 100644 index 0000000000..b3081efc62 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpPreferDictionaryContainsMethodsFixer.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.NetCore.Analyzers.Runtime; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.CodeActions; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + public sealed class CSharpPreferDictionaryContainsMethodsFixer : PreferDictionaryContainsMethodsFixer + { + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Document doc = context.Document; + SyntaxNode root = await doc.GetSyntaxRootAsync().ConfigureAwait(false); + + if (root.FindNode(context.Span) is not InvocationExpressionSyntax invocation) + return; + if (invocation.Expression is not MemberAccessExpressionSyntax containsMemberAccess) + return; + if (RemoveRoundBrackets(containsMemberAccess.Expression) is not MemberAccessExpressionSyntax keysOrValuesMemberAccess) + return; + + if (keysOrValuesMemberAccess.Name.Identifier.ValueText == KeysPropertyName) + { + var action = CodeAction.Create(ContainsKeyCodeFixTitle, ReplaceWithContainsKeyAsync, ContainsKeyCodeFixTitle); + context.RegisterCodeFix(action, context.Diagnostics); + } + else if (keysOrValuesMemberAccess.Name.Identifier.ValueText == ValuesPropertyName) + { + var action = CodeAction.Create(ContainsValueCodeFixTitle, ReplaceWithContainsValueAsync, ContainsValueCodeFixTitle); + context.RegisterCodeFix(action, context.Diagnostics); + } + + async Task ReplaceWithContainsKeyAsync(CancellationToken ct) + { + var editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); + var containsKeyMemberAccess = editor.Generator.MemberAccessExpression(keysOrValuesMemberAccess.Expression, ContainsKeyMethodName); + var newInvocation = editor.Generator.InvocationExpression(containsKeyMemberAccess, invocation.ArgumentList.Arguments); + editor.ReplaceNode(invocation, newInvocation); + + return editor.GetChangedDocument(); + } + + async Task ReplaceWithContainsValueAsync(CancellationToken ct) + { + var editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); + var containsValueMemberAccess = editor.Generator.MemberAccessExpression(keysOrValuesMemberAccess.Expression, ContainsValueMethodName); + var newInvocation = editor.Generator.InvocationExpression(containsValueMemberAccess, invocation.ArgumentList.Arguments); + editor.ReplaceNode(invocation, newInvocation); + + return editor.GetChangedDocument(); + } + } + + private static SyntaxNode RemoveRoundBrackets(SyntaxNode possiblyPerenthecizedDictionaryPropertyAccessExpression) + { + SyntaxNode current = possiblyPerenthecizedDictionaryPropertyAccessExpression; + while (current is ParenthesizedExpressionSyntax parenExpressionSyntax) + { + current = parenExpressionSyntax.Expression; + } + + return current; + } + } +} diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 3b90729293..0058139817 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1,4 +1 @@ -; Please do not edit this file manually, it should only be updated through code fix application.### New Rules -Rule ID | Category | Severity | Notes ---------|----------|----------|------- -CA1839 | Performance | Info | PreferDictionaryContainsMethods, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839) \ No newline at end of file +; Please do not edit this file manually, it should only be updated through code fix application. \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs index 6486ffeeb2..bc43290c8d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Immutable; -using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -12,57 +11,19 @@ namespace Microsoft.NetCore.Analyzers.Runtime { - [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] - public class PreferDictionaryContainsMethodsFixer : CodeFixProvider + public abstract class PreferDictionaryContainsMethodsFixer : CodeFixProvider { - public override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - Document doc = context.Document; - SyntaxNode root = await doc.GetSyntaxRootAsync().ConfigureAwait(false); - SyntaxNode invocation = root.FindNode(context.Span); - SyntaxNode containsMemberAccess = invocation.ChildNodes().First(); - SyntaxNode argumentList = invocation.ChildNodes().ElementAt(1); - SyntaxNode keysOrValuesMemberAccess = containsMemberAccess.ChildNodes().First(); - SyntaxNode receiver = keysOrValuesMemberAccess.ChildNodes().First(); - SyntaxNode propertyIdentifier = keysOrValuesMemberAccess.ChildNodes().ElementAt(1); + protected static string KeysPropertyName => PreferDictionaryContainsMethods.KeysPropertyName; - if (propertyIdentifier.GetText().ToString() == PreferDictionaryContainsMethods.KeysPropertyName) - { - var action = CodeAction.Create( - MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsKeyCodeFixTitle, - ReplaceKeysContainsWithContainsKeyAsync, - MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsKeyCodeFixTitle); - context.RegisterCodeFix(action, context.Diagnostics); - } - else if (propertyIdentifier.GetText().ToString() == PreferDictionaryContainsMethods.ValuesPropertyName) - { - var action = CodeAction.Create( - MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueCodeFixTitle, - ReplaceValuesContainsWithContainsValueAsync, - MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueCodeFixTitle); - context.RegisterCodeFix(action, context.Diagnostics); - } + protected static string ValuesPropertyName => PreferDictionaryContainsMethods.ValuesPropertyName; - async Task ReplaceKeysContainsWithContainsKeyAsync(CancellationToken ct) - { - var editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); - var containsKeyMemberAccess = editor.Generator.MemberAccessExpression(receiver, PreferDictionaryContainsMethods.ContainsKeyMethodName); - var newInvocation = editor.Generator.InvocationExpression(containsKeyMemberAccess, argumentList.ChildNodes()); - editor.ReplaceNode(invocation, newInvocation); + protected static string ContainsKeyMethodName => PreferDictionaryContainsMethods.ContainsKeyMethodName; - return editor.GetChangedDocument(); - } + protected static string ContainsValueMethodName => PreferDictionaryContainsMethods.ContainsValueMethodName; - async Task ReplaceValuesContainsWithContainsValueAsync(CancellationToken ct) - { - var editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); - var containsValueMemberAccess = editor.Generator.MemberAccessExpression(receiver, PreferDictionaryContainsMethods.ContainsValueMethodName); - var newInvocation = editor.Generator.InvocationExpression(containsValueMemberAccess, argumentList.ChildNodes()); - editor.ReplaceNode(invocation, newInvocation); + protected static string ContainsKeyCodeFixTitle => MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsKeyCodeFixTitle; - return editor.GetChangedDocument(); - } - } + protected static string ContainsValueCodeFixTitle => MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueCodeFixTitle; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(PreferDictionaryContainsMethods.RuleId); diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs index 0ba0300359..5dd74e17e7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs @@ -22,12 +22,13 @@ public sealed class PreferDictionaryContainsMethods : DiagnosticAnalyzer private static readonly LocalizableString s_localizableContainsValueMessage = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueMessage)); private static readonly LocalizableString s_localizableContainsValueDescription = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueDescription)); +#pragma warning disable RS2000 internal static readonly DiagnosticDescriptor ContainsKeyRule = DiagnosticDescriptorHelper.Create( RuleId, s_localizableTitle, s_localizableContainsKeyMessage, DiagnosticCategory.Performance, - RuleLevel.BuildWarningCandidate, + RuleLevel.BuildWarning, s_localizableContainsKeyDescription, isPortedFxCopRule: false, isDataflowRule: false); @@ -37,10 +38,11 @@ public sealed class PreferDictionaryContainsMethods : DiagnosticAnalyzer s_localizableTitle, s_localizableContainsValueMessage, DiagnosticCategory.Performance, - RuleLevel.BuildWarningCandidate, + RuleLevel.BuildWarning, s_localizableContainsValueDescription, isPortedFxCopRule: false, isDataflowRule: false); +#pragma warning restore RS2000 private const string ContainsMethodName = "Contains"; internal const string ContainsKeyMethodName = "ContainsKey"; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 36d4bd69e9..396d8f698a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index d02260ee46..7a886f8276 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 11c4287b08..97236cd611 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 4035e68aa2..8c46a02ef6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index b69eb0dc07..10e1871a45 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 09113b9107..9cb9e6f498 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index de08e31d31..96add03da9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 17790a1267..50b20598d6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 9b6150b68d..09aa64f730 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 07c4fdd87c..df37ede8f1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 088fcb592c..e0d630b517 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 994a1f0f2c..bf93f8503a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 65a18c1ba3..86e848f1a2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -1608,8 +1608,8 @@ - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. - It is often faster to call '{0}.ContainsKey(TKey)' than '{0}.Keys.Contains(TKey)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Keys'. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. + '{0}.ContainsKey(TKey)' is usually O(1), while '{0}.Keys.Contains(TKey)' may be O(n) in some cases. Additionally, many dictionary implementations lazily initialize the Keys collection to cut back on allocations. @@ -1628,8 +1628,8 @@ - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. - It is often faster to call '{0}.ContainsValue(TValue)' than '{0}.Values.Contains(TValue)'. It can also save allocations, as many dictionary implementations lazily instantiate 'Values'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethodsTests.cs index 5c1fb224c0..9eb10699e6 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethodsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethodsTests.cs @@ -1,27 +1,51 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Testing; -using Test.Utilities; using Xunit; + using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< Microsoft.NetCore.Analyzers.Runtime.PreferDictionaryContainsMethods, - Microsoft.NetCore.Analyzers.Runtime.PreferDictionaryContainsMethodsFixer>; + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpPreferDictionaryContainsMethodsFixer>; using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< Microsoft.NetCore.Analyzers.Runtime.PreferDictionaryContainsMethods, - Microsoft.NetCore.Analyzers.Runtime.PreferDictionaryContainsMethodsFixer>; + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicPreferDictionaryContainsMethodsFixer>; namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests { public class PreferDictionaryContainsMethodsTests { + #region Test Data + public static IEnumerable DictionaryKeysExpressions + { + get + { + yield return new object[] { "dictionary.Keys" }; + yield return new object[] { "(dictionary.Keys)" }; + yield return new object[] { "((dictionary.Keys))" }; + } + } + + public static IEnumerable DictionaryValuesExpressions + { + get + { + yield return new object[] { "dictionary.Values" }; + yield return new object[] { "(dictionary.Values)" }; + yield return new object[] { "((dictionary.Values))" }; + } + } + #endregion + #region Expected Diagnostic - [Fact] - public async Task IDictionary_Keys_Contains_ReportsDiagnostic_CS() + [Theory] + [MemberData(nameof(DictionaryKeysExpressions))] + public async Task IDictionary_Keys_Contains_ReportsDiagnostic_CS(string dictionaryKeys) { const string declaration = @"IDictionary dictionary = new Dictionary();"; - string testCode = CreateCSSource(declaration, @"bool wutzHuh = {|#0:dictionary.Keys.Contains(""Wumphed"")|};"); + string testCode = CreateCSSource(declaration, @$"bool wutzHuh = {{|#0:{dictionaryKeys}.Contains(""Wumphed"")|}};"); string fixedCode = CreateCSSource(declaration, @"bool wutzHuh = dictionary.ContainsKey(""Wumphed"");"); var diagnostic = VerifyCS.Diagnostic(ContainsKeyRule) .WithLocation(0) @@ -55,11 +79,12 @@ public async Task IDictionary_Keys_Contains_ReportsDiagnostic_VB() }.RunAsync(); } - [Fact] - public async Task BuiltInDictionary_Keys_Contains_ReportsDiagnostic_CS() + [Theory] + [MemberData(nameof(DictionaryKeysExpressions))] + public async Task BuiltInDictionary_Keys_Contains_ReportsDiagnostic_CS(string dictionaryKeys) { const string declaration = @"var dictionary = new Dictionary();"; - string testCode = CreateCSSource(declaration, @"bool isPhleeb = {|#0:dictionary.Keys.Contains(""bizzurf"")|};"); + string testCode = CreateCSSource(declaration, $@"bool isPhleeb = {{|#0:{dictionaryKeys}.Contains(""bizzurf"")|}};"); string fixedCode = CreateCSSource(declaration, @"bool isPhleeb = dictionary.ContainsKey(""bizzurf"");"); var diagnostic = VerifyCS.Diagnostic(ContainsKeyRule) .WithLocation(0) @@ -93,11 +118,12 @@ public async Task BuiltInDictionary_Keys_Contains_ReportsDiagnostic_VB() }.RunAsync(); } - [Fact] - public async Task BuiltInDictionary_Values_Contains_ReportsDiagnostic_CS() + [Theory] + [MemberData(nameof(DictionaryValuesExpressions))] + public async Task BuiltInDictionary_Values_Contains_ReportsDiagnostic_CS(string dictionaryValues) { const string declaration = "var dictionary = new Dictionary();"; - string testCode = CreateCSSource(declaration, @"bool wasFlobbed = {|#0:dictionary.Values.Contains(75)|};"); + string testCode = CreateCSSource(declaration, $@"bool wasFlobbed = {{|#0:{dictionaryValues}.Contains(75)|}};"); string fixedCode = CreateCSSource(declaration, @"bool wasFlobbed = dictionary.ContainsValue(75);"); var diagnostic = VerifyCS.Diagnostic(ContainsValueRule) .WithLocation(0) @@ -131,11 +157,12 @@ public async Task BuiltInDictionary_Values_Contains_ReportsDiagnostic_VB() }.RunAsync(); } - [Fact] - public async Task ExplicitContainsKey_WhenTypedAsIDictionary_ReportsDiagnostic_CS() + [Theory] + [MemberData(nameof(DictionaryKeysExpressions))] + public async Task ExplicitContainsKey_WhenTypedAsIDictionary_ReportsDiagnostic_CS(string dictionaryKeys) { const string declaration = "IDictionary dictionary = new TestDictionary();"; - string testCode = CreateCSSource(declaration, @"bool tugBlug = {|#0:dictionary.Keys.Contains(""bricklebrit"")|};"); + string testCode = CreateCSSource(declaration, $@"bool tugBlug = {{|#0:{dictionaryKeys}.Contains(""bricklebrit"")|}};"); string fixedCode = CreateCSSource(declaration, @"bool tugBlug = dictionary.ContainsKey(""bricklebrit"");"); var diagnostic = VerifyCS.Diagnostic(ContainsKeyRule) .WithLocation(0) @@ -168,6 +195,84 @@ public async Task ExplicitContainsKey_WhenTypedAsIDictionary_ReportsDiagnostic_V ExpectedDiagnostics = { diagnostic } }.RunAsync(); } + + [Theory] + [MemberData(nameof(DictionaryKeysExpressions))] + public async Task IEnumerableKeyCollection_ReportsDiagnostic_CS(string dictionaryKeys) + { + const string declaration = "var dictionary = new TestDictionary();"; + string testCode = CreateCSSource(declaration, $@"bool b = {{|#0:{dictionaryKeys}.Contains(""bracklebrat"")|}};"); + string fixedCode = CreateCSSource(declaration, @"bool b = dictionary.ContainsKey(""bracklebrat"");"); + var diagnostic = VerifyCS.Diagnostic(ContainsKeyRule) + .WithLocation(0) + .WithArguments("TestDictionary"); + + await new VerifyCS.Test + { + TestState = { Sources = { testCode, CSCustomFacadeCollectionsDictionarySource, CSIEnumerableFacadeCollectionsSource } }, + FixedState = { Sources = { fixedCode, CSCustomFacadeCollectionsDictionarySource, CSIEnumerableFacadeCollectionsSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task IEnumerableKeyCollection_ReportsDiagnostic_VB() + { + const string declaration = "Dim dictionary = New TestDictionary()"; + string testCode = CreateVBSource(declaration, @"Dim b = {|#0:dictionary.Keys.Contains(""bracklebrat"")|}"); + string fixedCode = CreateVBSource(declaration, @"Dim b = dictionary.ContainsKey(""bracklebrat"")"); + var diagnostic = VerifyVB.Diagnostic(ContainsKeyRule) + .WithLocation(0) + .WithArguments("TestDictionary"); + + await new VerifyVB.Test + { + TestState = { Sources = { testCode, VBCustomFacadeCollectionsDictionarySource, VBIEnumerableFacadeCollectionsSource } }, + FixedState = { Sources = { fixedCode, VBCustomFacadeCollectionsDictionarySource, VBIEnumerableFacadeCollectionsSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Theory] + [MemberData(nameof(DictionaryValuesExpressions))] + public async Task IEnumerableValueCollection_ReportsDiagnostic_CS(string dictionaryValues) + { + const string declaration = "var dictionary = new TestDictionary();"; + string testCode = CreateCSSource(declaration, $@"bool b = {{|#0:{dictionaryValues}.Contains(314159)|}};"); + string fixedCode = CreateCSSource(declaration, @"bool b = dictionary.ContainsValue(314159);"); + var diagnostic = VerifyCS.Diagnostic(ContainsValueRule) + .WithLocation(0) + .WithArguments("TestDictionary"); + + await new VerifyCS.Test + { + TestState = { Sources = { testCode, CSCustomFacadeCollectionsDictionarySource, CSIEnumerableFacadeCollectionsSource } }, + FixedState = { Sources = { fixedCode, CSCustomFacadeCollectionsDictionarySource, CSIEnumerableFacadeCollectionsSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task IEnumerableValueCollection_ReportsDiagnostic_VB() + { + const string declaration = "Dim dictionary = New TestDictionary()"; + string testCode = CreateVBSource(declaration, @"Dim b = {|#0:dictionary.Values.Contains(314159)|}"); + string fixedCode = CreateVBSource(declaration, @"Dim b = dictionary.ContainsValue(314159)"); + var diagnostic = VerifyVB.Diagnostic(ContainsValueRule) + .WithLocation(0) + .WithArguments("TestDictionary"); + + await new VerifyVB.Test + { + TestState = { Sources = { testCode, VBCustomFacadeCollectionsDictionarySource, VBIEnumerableFacadeCollectionsSource } }, + FixedState = { Sources = { fixedCode, VBCustomFacadeCollectionsDictionarySource, VBIEnumerableFacadeCollectionsSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } #endregion #region No Diagnostic @@ -224,6 +329,42 @@ public async Task ExplicitContainsKey_Keys_Contains_NoDiagnostic_VB() ReferenceAssemblies = ReferenceAssemblies.Net.Net50 }.RunAsync(); } + + [Theory] + [InlineData(CSFacadeCollectionContainsWithWrongArgumentTypeSource, @"dictionary.Keys.Contains(29)")] + [InlineData(CSFacadeCollectionContainsWithWrongArgumentTypeSource, @"dictionary.Values.Contains(""RuhRoh"")")] + [InlineData(CSFacadeCollectionContainsWithBaseTypeArgumentSource, @"dictionary.Keys.Contains(new object())")] + [InlineData(CSFacadeCollectionContainsWithBaseTypeArgumentSource, @"dictionary.Values.Contains(new object())")] + public async Task ContainsArgument_WrongType_NoDiagnostic_CS(string facadeCollectionSource, string containsInvocation) + { + string testCode = CreateCSSourceWithoutLinq( + @"var dictionary = new TestDictionary();", + $@"bool b = {containsInvocation};"); + + await new VerifyCS.Test + { + TestState = { Sources = { testCode, CSCustomFacadeCollectionsDictionarySource, facadeCollectionSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + + [Theory] + [InlineData(VBFacadeCollectionContainsWithWrongArgumentTypeSource, @"dictionary.Keys.Contains(29)")] + [InlineData(VBFacadeCollectionContainsWithWrongArgumentTypeSource, @"dictionary.Values.Contains(""RuhRoh"")")] + [InlineData(VBFacadeCollectionContainsWithBaseTypeArgumentSource, @"dictionary.Keys.Contains(New Object())")] + [InlineData(VBFacadeCollectionContainsWithBaseTypeArgumentSource, @"dictionary.Values.Contains(New Object())")] + public async Task ContainsArgument_WrongType_NoDiagnostic_VB(string facadeCollectionSource, string containsInvocation) + { + string testCode = CreateVBSourceWithoutLinq( + @"Dim dictionary = New TestDictionary()", + $@"Dim b = {containsInvocation}"); + + await new VerifyVB.Test + { + TestState = { Sources = { testCode, VBCustomFacadeCollectionsDictionarySource, facadeCollectionSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } #endregion #region Helpers @@ -268,6 +409,45 @@ End Namespace return string.Format(System.Globalization.CultureInfo.InvariantCulture, body, string.Join("\r\n ", statements)); } + private static string CreateCSSourceWithoutLinq(params string[] statements) + { + string body = @" +using System; +using System.Collections.Generic; + +namespace Testopolis +{{ + public class WumphingWumbler + {{ + public void MegaWumph() + {{ + {0} + }} + }} +}} +"; + return string.Format(System.Globalization.CultureInfo.InvariantCulture, body, string.Join("\r\n ", statements)); + } + + private static string CreateVBSourceWithoutLinq(params string[] statements) + { + string body = @" +Imports System +Imports System.Collections.Generic + +Namespace Testopolis + + Public Class WumphingWumbler + + Public Sub MegaWumph() + {0} + End Sub + End Class +End Namespace +"; + return string.Format(System.Globalization.CultureInfo.InvariantCulture, body, string.Join("\r\n ", statements)); + } + private const string CSExplicitContainsKeyDictionarySource = @" using System; using System.Collections; @@ -436,6 +616,440 @@ End Function End Class End Namespace"; + /// + /// Source code that defines an IDictionary(string, int) implementation of type 'TestDictionary' that exposes Keys and Values + /// properties of type 'KeyCollection' and 'ValueCollection', respectively. + /// The corrasponding properties on IDictionary`2 are implemented explicitly. + /// + private const string CSCustomFacadeCollectionsDictionarySource = @" +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Testopolis +{ + public class TestDictionary : IDictionary + { + public KeyCollection Keys { get; } + + public ValueCollection Values { get; } + + ICollection IDictionary.Keys { get; } + + ICollection IDictionary.Values { get; } + + public bool ContainsKey(string key) + { + throw new NotImplementedException(); + } + + public bool ContainsValue(int value) + { + throw new NotImplementedException(); + } + + public void Add(string key, int value) + { + throw new NotImplementedException(); + } + + public bool Remove(string key) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(string key, out int value) + { + throw new NotImplementedException(); + } + + public int this[string key] + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public void Add(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public bool Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public bool Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public int Count { get; } + public bool IsReadOnly { get; } + + public IEnumerator> GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } +} +"; + + /// + /// Defines 'KeyCollection' and 'ValueCollection' types that implement IEnumerable`1 but not ICollection`1. + /// + private const string CSIEnumerableFacadeCollectionsSource = @" +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Testopolis +{ + public class KeyCollection : IEnumerable + { + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + public class ValueCollection : IEnumerable + { + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } +} +"; + + private const string CSFacadeCollectionContainsWithWrongArgumentTypeSource = @" +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Testopolis +{ + public class KeyCollection : IEnumerable + { + public bool Contains(int wrongType) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + public class ValueCollection : IEnumerable + { + public bool Contains(string wrongType) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } +} +"; + + private const string CSFacadeCollectionContainsWithBaseTypeArgumentSource = @" +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Testopolis +{ + public class KeyCollection : IEnumerable + { + public bool Contains(object tooGeneral) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + public class ValueCollection : IEnumerable + { + public bool Contains(object tooGeneral) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } +} +"; + + private const string VBCustomFacadeCollectionsDictionarySource = @" +Imports System +Imports System.Collections +Imports System.Collections.Generic + +Namespace Testopolis + Public Class TestDictionary : Implements IDictionary(Of String, Integer) + + Public ReadOnly Property Keys As KeyCollection + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property Values As ValueCollection + Get + Throw New NotImplementedException() + End Get + End Property + + Private ReadOnly Property ICollection_Keys As ICollection(Of String) Implements IDictionary(Of String, Integer).Keys + Get + Throw New NotImplementedException() + End Get + End Property + + Private ReadOnly Property ICollection_Values As ICollection(Of Integer) Implements IDictionary(Of String, Integer).Values + Get + Throw New NotImplementedException() + End Get + End Property + + Default Public Property Item(key As String) As Integer Implements IDictionary(Of String, Integer).Item + Get + Throw New NotImplementedException() + End Get + Set(value As Integer) + Throw New NotImplementedException() + End Set + End Property + + Public ReadOnly Property Count As Integer Implements ICollection(Of KeyValuePair(Of String, Integer)).Count + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of KeyValuePair(Of String, Integer)).IsReadOnly + Get + Throw New NotImplementedException() + End Get + End Property + + Public Sub Add(key As String, value As Integer) Implements IDictionary(Of String, Integer).Add + Throw New NotImplementedException() + End Sub + + Public Sub Add(item As KeyValuePair(Of String, Integer)) Implements ICollection(Of KeyValuePair(Of String, Integer)).Add + Throw New NotImplementedException() + End Sub + + Public Sub Clear() Implements ICollection(Of KeyValuePair(Of String, Integer)).Clear + Throw New NotImplementedException() + End Sub + + Public Sub CopyTo(array() As KeyValuePair(Of String, Integer), arrayIndex As Integer) Implements ICollection(Of KeyValuePair(Of String, Integer)).CopyTo + Throw New NotImplementedException() + End Sub + + Public Function ContainsKey(key As String) As Boolean Implements IDictionary(Of String, Integer).ContainsKey + Throw New NotImplementedException() + End Function + + Public Function ContainsValue(value As Integer) As Boolean + Throw New NotImplementedException() + End Function + + Public Function Remove(key As String) As Boolean Implements IDictionary(Of String, Integer).Remove + Throw New NotImplementedException() + End Function + + Public Function Remove(item As KeyValuePair(Of String, Integer)) As Boolean Implements ICollection(Of KeyValuePair(Of String, Integer)).Remove + Throw New NotImplementedException() + End Function + + Public Function TryGetValue(key As String, ByRef value As Integer) As Boolean Implements IDictionary(Of String, Integer).TryGetValue + Throw New NotImplementedException() + End Function + + Public Function Contains(item As KeyValuePair(Of String, Integer)) As Boolean Implements ICollection(Of KeyValuePair(Of String, Integer)).Contains + Throw New NotImplementedException() + End Function + + Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of String, Integer)) Implements IEnumerable(Of KeyValuePair(Of String, Integer)).GetEnumerator + Throw New NotImplementedException() + End Function + + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class +End Namespace +"; + + private const string VBIEnumerableFacadeCollectionsSource = @" +Imports System +Imports System.Collections +Imports System.Collections.Generic + +Namespace Testopolis + Public Class KeyCollection : Implements IEnumerable(Of String) + + Public Function GetEnumerator() As IEnumerator(Of String) Implements IEnumerable(Of String).GetEnumerator + Throw New NotImplementedException() + End Function + + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class + + Public Class ValueCollection : Implements IEnumerable(Of Integer) + + Public Function GetEnumerator() As IEnumerator(Of Integer) Implements IEnumerable(Of Integer).GetEnumerator + Throw New NotImplementedException() + End Function + + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class +End Namespace +"; + + private const string VBFacadeCollectionContainsWithWrongArgumentTypeSource = @" +Imports System +Imports System.Collections +Imports System.Collections.Generic + +Namespace Testopolis + + Public Class KeyCollection : Implements IEnumerable(Of String) + + Public Function Contains(wrongType As Integer) As Boolean + Throw New NotImplementedException() + End Function + + Public Function GetEnumerator() As IEnumerator(Of String) Implements IEnumerable(Of String).GetEnumerator + Throw New NotImplementedException() + End Function + + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class + + Public Class ValueCollection : Implements IEnumerable(Of Integer) + + Public Function Contains(wrongType As String) As Boolean + Throw New NotImplementedException() + End Function + + Public Function GetEnumerator() As IEnumerator(Of Integer) Implements IEnumerable(Of Integer).GetEnumerator + Throw New NotImplementedException() + End Function + + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class +End Namespace +"; + + private const string VBFacadeCollectionContainsWithBaseTypeArgumentSource = @" +Imports System +Imports System.Collections +Imports System.Collections.Generic + +Namespace Testopolis + + Public Class KeyCollection : Implements IEnumerable(Of String) + + Public Function Contains(tooGeneral As Object) As Boolean + Throw New NotImplementedException() + End Function + + Public Function GetEnumerator() As IEnumerator(Of String) Implements IEnumerable(Of String).GetEnumerator + Throw New NotImplementedException() + End Function + + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class + + Public Class ValueCollection : Implements IEnumerable(Of Integer) + + Public Function Contains(tooGeneral As Object) As Boolean + Throw New NotImplementedException() + End Function + + Public Function GetEnumerator() As IEnumerator(Of Integer) Implements IEnumerable(Of Integer).GetEnumerator + Throw New NotImplementedException() + End Function + + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class +End Namespace +"; + private static DiagnosticDescriptor ContainsKeyRule => PreferDictionaryContainsMethods.ContainsKeyRule; private static DiagnosticDescriptor ContainsValueRule => PreferDictionaryContainsMethods.ContainsValueRule; #endregion diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicPreferDictionaryContainsMethodsFixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicPreferDictionaryContainsMethodsFixer.vb new file mode 100644 index 0000000000..41e71f884a --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicPreferDictionaryContainsMethodsFixer.vb @@ -0,0 +1,61 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports Microsoft.NetCore.Analyzers.Runtime +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports System.Threading +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.CodeActions + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + + Public NotInheritable Class BasicPreferDictionaryContainsMethodsFixer : Inherits PreferDictionaryContainsMethodsFixer + + Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + Dim doc = context.Document + Dim root = Await doc.GetSyntaxRootAsync().ConfigureAwait(False) + + Dim invocation = TryCast(root.FindNode(context.Span), InvocationExpressionSyntax) + If invocation Is Nothing Then + Return + End If + + Dim containsMemberAccess = TryCast(invocation.Expression, MemberAccessExpressionSyntax) + If containsMemberAccess Is Nothing Then + Return + End If + + Dim keysOrValuesMember = TryCast(containsMemberAccess.Expression, MemberAccessExpressionSyntax) + If keysOrValuesMember Is Nothing Then + Return + End If + + If keysOrValuesMember.Name.Identifier.ValueText = KeysPropertyName Then + Dim ReplaceWithContainsKey = + Async Function(ct As CancellationToken) As Task(Of Document) + Dim editor = Await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(False) + Dim containsKeyMemberExpression = editor.Generator.MemberAccessExpression(keysOrValuesMember.Expression, ContainsKeyMethodName) + Dim newInvocation = editor.Generator.InvocationExpression(containsKeyMemberExpression, invocation.ArgumentList.Arguments) + editor.ReplaceNode(invocation, newInvocation) + + Return editor.GetChangedDocument() + End Function + Dim action = CodeAction.Create(ContainsKeyCodeFixTitle, ReplaceWithContainsKey, ContainsKeyCodeFixTitle) + context.RegisterCodeFix(action, context.Diagnostics) + + ElseIf keysOrValuesMember.Name.Identifier.ValueText = ValuesPropertyName Then + Dim ReplaceWithContainsValue = + Async Function(ct As CancellationToken) As Task(Of Document) + Dim editor = Await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(False) + Dim containsValueMemberExpression = editor.Generator.MemberAccessExpression(keysOrValuesMember.Expression, ContainsValueMethodName) + Dim newInvocation = editor.Generator.InvocationExpression(containsValueMemberExpression, invocation.ArgumentList.Arguments) + editor.ReplaceNode(invocation, newInvocation) + + Return editor.GetChangedDocument() + End Function + Dim action = CodeAction.Create(ContainsValueCodeFixTitle, ReplaceWithContainsValue, ContainsValueCodeFixTitle) + context.RegisterCodeFix(action, context.Diagnostics) + End If + End Function + End Class +End Namespace From 740fbcb24fc471b98bdbc44093f4ba58bdd8599c Mon Sep 17 00:00:00 2001 From: NewellClark Date: Tue, 12 Jan 2021 16:41:07 -0500 Subject: [PATCH 009/224] Fixed warnings - Removed unused usings - Fixed error in AnalyzerReleases.Unshipped.md --- .../Core/AnalyzerReleases.Unshipped.md | 5 +- .../PreferDictionaryContainsMethods.Fixer.cs | 6 --- .../PreferDictionaryContainsMethods.cs | 49 +++++++++++++++++-- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 0058139817..82137589b6 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1 +1,4 @@ -; Please do not edit this file manually, it should only be updated through code fix application. \ No newline at end of file +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +CA1839 | Performance | Warning | PreferDictionaryContainsMethods, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839) \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs index bc43290c8d..37a71776d8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.Fixer.cs @@ -1,13 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Editing; namespace Microsoft.NetCore.Analyzers.Runtime { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs index 5dd74e17e7..342dc7f4ec 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferDictionaryContainsMethods.cs @@ -22,7 +22,6 @@ public sealed class PreferDictionaryContainsMethods : DiagnosticAnalyzer private static readonly LocalizableString s_localizableContainsValueMessage = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueMessage)); private static readonly LocalizableString s_localizableContainsValueDescription = CreateResource(nameof(MicrosoftNetCoreAnalyzersResources.PreferDictionaryContainsValueDescription)); -#pragma warning disable RS2000 internal static readonly DiagnosticDescriptor ContainsKeyRule = DiagnosticDescriptorHelper.Create( RuleId, s_localizableTitle, @@ -42,7 +41,6 @@ public sealed class PreferDictionaryContainsMethods : DiagnosticAnalyzer s_localizableContainsValueDescription, isPortedFxCopRule: false, isDataflowRule: false); -#pragma warning restore RS2000 private const string ContainsMethodName = "Contains"; internal const string ContainsKeyMethodName = "ContainsKey"; @@ -74,6 +72,30 @@ private static void OnCompilationStart(CompilationStartAnalysisContext compilati void OnOperationAction(OperationAnalysisContext context) { + // We report a diagnostic if we find an invocation of an applicable Contains method, + // and the contains method is being invoked on an applicable property. + + // A property is applicable if: + // 1) It belongs to a type that implements IDictionary`2 + // 2) It's name is either "Keys" or "Values" + // A Contains method is applicable if: + // 1) It has a boolean return type. + // 2) It has one argument, not counting the first argument of an extension method. + // 3) The argument must match the applicable type argument. + // - If the property's name is "Keys", it must match the type substituted for TKey in the IDictionary`2 instance. + // - If the property's name is "Values", it must match the type substituted for TValue in the IDictionary`2 instance. + + // Once we have an applicable Contains method and an applicable property reference, we search for an applicable ContainsXXX + // method on the IDictionary`2 receiver. + + // A ContainsXXX method is applicable if: + // 1) It has a boolean return type + // 2) It is publically accessible + // 3) Its name is "ContainsKey" if property is "Keys", or "ContainsValue" if property is "Values". + // 4) It has exactly one parameter of the correct type: + // - If the method is a "ContainsKey" method, its type must be TKey. + // - If the method is a "ContainsValue" method, its type must be TValue. + var invocation = (IInvocationOperation)context.Operation; IMethodSymbol containsMethod = invocation.TargetMethod; @@ -88,8 +110,16 @@ void OnOperationAction(OperationAnalysisContext context) if (!TryGetConstructedDictionaryType(property.ContainingType, out INamedTypeSymbol? constructedDictionaryType)) return; + // At this point, we know that the method being invoked is a method called "Contains" that has a boolean return type. + // We also know that the method is being invoked on a property belonging to a type that implements IDictionary`2. We will + // compare the types used to construct IDictionary`2 to the parameter type of the Contains method. + ITypeSymbol keyType = constructedDictionaryType.TypeArguments[0]; ITypeSymbol valueType = constructedDictionaryType.TypeArguments[1]; + + // We use Parameters.Last() because the first argument could be the key/value collection, depending + // on whether the method is an extension method and whether the language is C# or Visual Basic. + ITypeSymbol containsParameterType = containsMethod.Parameters.Last().Type; if (property.Name == KeysPropertyName) @@ -97,6 +127,9 @@ void OnOperationAction(OperationAnalysisContext context) if (!containsParameterType.Equals(keyType, SymbolEqualityComparer.Default)) return; + // Now we search for an accessible ContainsKey method that returns boolean and accepts a single + // parameter of the type that was substituted for TKey. + IMethodSymbol? containsKeyMethod = property.ContainingType.GetMembers(ContainsKeyMethodName) .OfType() .WhereAsArray(x => x.ReturnType.SpecialType == SpecialType.System_Boolean && @@ -116,6 +149,9 @@ void OnOperationAction(OperationAnalysisContext context) if (!containsParameterType.Equals(valueType, SymbolEqualityComparer.Default)) return; + // Now we search for an accessible ContainsValue method that returns boolean and accepts a single + // parameter of the type that was substituted for TValue. + IMethodSymbol? containsValueMethod = property.ContainingType.GetMembers(ContainsValueMethodName) .OfType() .WhereAsArray(x => x.ReturnType.SpecialType == SpecialType.System_Boolean && @@ -133,8 +169,12 @@ void OnOperationAction(OperationAnalysisContext context) static bool TryGetPropertyReferenceOperation(IInvocationOperation containsInvocation, [NotNullWhen(true)] out IPropertySymbol? property) { - IOperation current = containsInvocation; + // If the method being invoked is an extension method, then the first child may be an IArgumentOperation, + // and its first child may be an IConversionOperation. The first child is not guaranteed to be an IArgumentOperation + // because VB and C# handle extension methods differently. Likewise, the IConversionOperation may be absent if + // the compile-time types match exactly. Therefore, we need to walk down conditionally. + IOperation current = containsInvocation; if (containsInvocation.TargetMethod.IsExtensionMethod) { if (current.Children.FirstOrDefault() is IArgumentOperation argumentOperation) @@ -165,6 +205,9 @@ bool TryGetConstructedDictionaryType(INamedTypeSymbol derived, [NotNullWhen(true static bool DoesContainsCandidateHaveCorrectArgumentCount(IMethodSymbol containsCandidate) { + // C# and VB handle extension methods differently. In C#, the extended 'this' parameter will be included in + // the argument list, whereas in VB, it will not. + if (containsCandidate.Language == LanguageNames.CSharp && containsCandidate.IsExtensionMethod) return containsCandidate.Parameters.Length == 2; else From a3622e9eebc4d4ad6c74e093e4d152187518f93c Mon Sep 17 00:00:00 2001 From: NewellClark Date: Tue, 12 Jan 2021 17:27:16 -0500 Subject: [PATCH 010/224] Fixed merge conflicts, ran MSBuild --- .../Core/AnalyzerReleases.Unshipped.md | 4 ++++ .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 +++++++++++ .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 +++++++++++++++++++ src/NetAnalyzers/RulesMissingDocumentation.md | 1 + 4 files changed, 37 insertions(+) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index cdf4f1397e..ba2a5c0f07 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1 +1,5 @@ ; Please do not edit this file manually, it should only be updated through code fix application. +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +CA1839 | Performance | Warning | PreferDictionaryContainsMethods, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839) \ No newline at end of file diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 1dff4237dc..b68edc9ee2 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1308,6 +1308,18 @@ Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in |CodeFix|False| --- +## [CA1839](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839): Prefer Dictionary Contains Methods + +Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Warning| +|CodeFix|True| +--- + ## [CA2000](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope 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. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 017f323cab..603f67d450 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -2462,6 +2462,26 @@ ] } }, + "CA1839": { + "id": "CA1839", + "shortDescription": "Prefer Dictionary Contains Methods", + "fullDescription": "Many dictionary implementations lazily initialize the Values collection. To avoid unnecessary allocations, prefer '{0}.ContainsValue(TValue)' over '{0}.Values.Contains(TValue)'.", + "defaultLevel": "warning", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "PreferDictionaryContainsMethods", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 75e0f77588..59f46bd5ac 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,3 +2,4 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| +CA1839 | | Prefer Dictionary Contains Methods | From a67832e5fb5911403d07a3d748c6c71d2eeea0b1 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Fri, 15 Jan 2021 12:48:50 -0500 Subject: [PATCH 011/224] Implement analyzer with basic tests --- .../Core/AnalyzerReleases.Unshipped.md | 5 + .../MicrosoftNetCoreAnalyzersResources.resx | 10 + .../ProvideStreamMemoryBasedAsyncOverrides.cs | 192 +++++++++++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 15 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 15 + .../ProvideStreamMemoryAsyncOverridesTests.cs | 299 ++++++++++++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- 18 files changed, 702 insertions(+), 1 deletion(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 64372afc0b..019a1608f5 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1,5 +1,10 @@ ; Please do not edit this file manually, it should only be updated through code fix application. +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +CA1840 | Performance | Info | ProvideStreamMemoryBasedAsyncOverrides, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840) + ### Removed Rules Rule ID | Category | Severity | Notes diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 0b8e62a9ba..d8942f4288 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1510,4 +1510,14 @@ and all other platforms This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs new file mode 100644 index 0000000000..abe21b860f --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Text; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public sealed class ProvideStreamMemoryBasedAsyncOverrides : DiagnosticAnalyzer + { + internal const string RuleId = "CA1840"; + + private static readonly LocalizableString s_localizableTitle = CreateResource(nameof(Resx.ProvideStreamMemoryBasedAsyncOverridesTitle)); + private static readonly LocalizableString s_localizableMessage = CreateResource(nameof(Resx.ProvideStreamMemoryBasedAsyncOverridesMessage)); + private static readonly LocalizableString s_localizableDescription = CreateResource(nameof(Resx.ProvideStreamMemoryBasedAsyncOverridesDescription)); + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + s_localizableTitle, + s_localizableMessage, + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + private const string ReadAsyncName = "ReadAsync"; + private const string WriteAsyncName = "WriteAsync"; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + var compilation = context.Compilation; + + if (!TryGetRequiredSymbols(compilation, out RequiredSymbols symbols)) + return; + + context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); + + void AnalyzeNamedType(SymbolAnalysisContext context) + { + var type = (INamedTypeSymbol)context.Symbol; + + if (!symbols.StreamType.Equals(type.BaseType, SymbolEqualityComparer.Default)) + return; + + IMethodSymbol? readAsyncArrayOverride = GetOverride(type, symbols.ReadAsyncArrayMethod); + IMethodSymbol? readAsyncMemoryOverride = GetOverride(type, symbols.ReadAsyncMemoryMethod); + IMethodSymbol? writeAsyncArrayOverride = GetOverride(type, symbols.WriteAsyncArrayMethod); + IMethodSymbol? writeAsyncMemoryOverride = GetOverride(type, symbols.WriteAsyncMemoryMethod); + + if (readAsyncArrayOverride is not null && readAsyncMemoryOverride is null) + { + var diagnostic = CreateDiagnostic(type, readAsyncArrayOverride, symbols.ReadAsyncMemoryMethod); + context.ReportDiagnostic(diagnostic); + } + + if (writeAsyncArrayOverride is not null && writeAsyncMemoryOverride is null) + { + var diagnostic = CreateDiagnostic(type, writeAsyncArrayOverride, symbols.WriteAsyncMemoryMethod); + context.ReportDiagnostic(diagnostic); + } + } + + static Diagnostic CreateDiagnostic(INamedTypeSymbol violatingType, IMethodSymbol arrayBasedOverride, IMethodSymbol memoryBasedMethod) + { + RoslynDebug.Assert(arrayBasedOverride.OverriddenMethod is not null); + + var location = violatingType.Locations.Single(x => x.SourceTree == arrayBasedOverride.Locations[0].SourceTree); + return Diagnostic.Create( + Rule, location, + violatingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + arrayBasedOverride.OverriddenMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + memoryBasedMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); + } + } + + private static bool TryGetRequiredSymbols(Compilation compilation, out RequiredSymbols requiredSymbols) + { + var int32Type = compilation.GetSpecialType(SpecialType.System_Int32); + var byteType = compilation.GetSpecialType(SpecialType.System_Byte); + var byteArrayType = compilation.CreateArrayTypeSymbol(byteType); + var memoryOfByteType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemory1)?.Construct(byteType); + var readOnlyMemoryOfByteType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1)?.Construct(byteType); + var streamType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOStream); + var cancellationTokenType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingCancellationToken); + + if (memoryOfByteType is null || readOnlyMemoryOfByteType is null || streamType is null || cancellationTokenType is null) + { + requiredSymbols = default; + return false; + } + + var readAsyncArrayMethod = GetOverload(streamType, ReadAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType); + var readAsyncMemoryMethod = GetOverload(streamType, ReadAsyncName, memoryOfByteType, cancellationTokenType); + var writeAsyncArrayMethod = GetOverload(streamType, WriteAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType); + var writeAsyncMemoryMethod = GetOverload(streamType, WriteAsyncName, readOnlyMemoryOfByteType, cancellationTokenType); + + if (readAsyncArrayMethod is null || readAsyncMemoryMethod is null || writeAsyncArrayMethod is null || writeAsyncMemoryMethod is null) + { + requiredSymbols = default; + return false; + } + + requiredSymbols = new RequiredSymbols( + streamType, memoryOfByteType, readOnlyMemoryOfByteType, + readAsyncArrayMethod, readAsyncMemoryMethod, + writeAsyncArrayMethod, writeAsyncMemoryMethod); + return true; + } + + private static IMethodSymbol? GetOverload(ITypeSymbol containingType, string methodName, params ITypeSymbol[] argumentTypes) + { + return containingType.GetMembers(methodName) + .SingleOrDefault(symbol => symbol is IMethodSymbol m && IsMatch(m, argumentTypes)) as IMethodSymbol; + + static bool IsMatch(IMethodSymbol method, ITypeSymbol[] argumentTypes) + { + if (method.Parameters.Length != argumentTypes.Length) + return false; + + for (int index = 0; index < argumentTypes.Length; ++index) + { + if (!argumentTypes[index].Equals(method.Parameters[index].Type, SymbolEqualityComparer.Default)) + return false; + } + + return true; + } + } + + private static IMethodSymbol? GetOverride(ITypeSymbol derivedType, IMethodSymbol overriddenMethod) + { + return derivedType.GetMembers(overriddenMethod.Name) + .SingleOrDefault(x => + { + return x.IsOverride && overriddenMethod.Equals(x.GetOverriddenMember(), SymbolEqualityComparer.Default); + }) as IMethodSymbol; + } + + // We will not be doing any comparisons on this type. +#pragma warning disable CA1815 // Override equals and operator equals on value types + private readonly struct RequiredSymbols +#pragma warning restore CA1815 // Override equals and operator equals on value types + { + public RequiredSymbols( + ITypeSymbol streamType, ITypeSymbol memoryOfByteType, ITypeSymbol readOnlyMemoryOfByteType, + IMethodSymbol readAsyncArrayMethod, IMethodSymbol readAsyncMemoryMethod, + IMethodSymbol writeAsyncArrayMethod, IMethodSymbol writeAsyncMemoryMethod) + { + StreamType = streamType; + MemoryOfByteType = memoryOfByteType; + ReadOnlyMemoryOfByteType = readOnlyMemoryOfByteType; + ReadAsyncArrayMethod = readAsyncArrayMethod; + ReadAsyncMemoryMethod = readAsyncMemoryMethod; + WriteAsyncArrayMethod = writeAsyncArrayMethod; + WriteAsyncMemoryMethod = writeAsyncMemoryMethod; + } + + public ITypeSymbol StreamType { get; } + public ITypeSymbol MemoryOfByteType { get; } + public ITypeSymbol ReadOnlyMemoryOfByteType { get; } + public IMethodSymbol ReadAsyncArrayMethod { get; } + public IMethodSymbol ReadAsyncMemoryMethod { get; } + public IMethodSymbol WriteAsyncArrayMethod { get; } + public IMethodSymbol WriteAsyncMemoryMethod { get; } + } + + private static LocalizableString CreateResource(string resourceName) + { + return new LocalizableResourceString(resourceName, Resx.ResourceManager, typeof(Resx)); + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 714e9e49b4..ebe70f5fe6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -1722,6 +1722,21 @@ Poskytujte metody deserializace pro volitelné pole + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Odebrat redundantní volání diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 6e0f153a85..6917a1ff08 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -1722,6 +1722,21 @@ Deserialisierungsmethoden für optionale Felder angeben + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Redundanten Aufruf entfernen diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 8c66938819..6b7771206b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -1722,6 +1722,21 @@ Proporcionar métodos de deserialización para campos opcionales + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Quitar llamada redundante diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 71a0c15261..bcb0917a63 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -1722,6 +1722,21 @@ Spécifiez des méthodes de désérialisation pour les champs facultatifs + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Supprimer un appel redondant diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 61adbbacb6..5745cfbba1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -1722,6 +1722,21 @@ Fornire metodi di deserializzazione per i campi facoltativi + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Rimuovi la chiamata ridondante diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 4faf2ebe51..e3f892ccd0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -1722,6 +1722,21 @@ 省略可能なフィールドに、逆シリアル化メソッドを指定します + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call 冗長な呼び出しを削除する diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 42e160e005..e58b87fd9b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -1722,6 +1722,21 @@ 선택적 필드에 deserialization 메서드를 제공하십시오. + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call 중복 호출 제거 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index dd9ad6db14..7a77f6bf51 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -1722,6 +1722,21 @@ Udostępnij metody deserializacji dla pól opcjonalnych + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Usuń nadmiarowe wywołanie diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index f73645c162..11cb90a1bc 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -1722,6 +1722,21 @@ Fornecer métodos de desserialização para campos opcionais + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Remova a chamada redundante diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 3b853eaf7e..1f528617dd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -1722,6 +1722,21 @@ Обеспечьте наличие методов десериализации в необязательных полях + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Удалить избыточный вызов diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index e85849b540..57a415bbdc 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -1722,6 +1722,21 @@ İsteğe bağlı yöntemler için serileştirme kaldırma yöntemler sağlayın + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call Gereksiz çağrıyı kaldır diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 2548f9f6f3..383f8fa001 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -1722,6 +1722,21 @@ 为可选字段提供反序列化方法 + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call 删除冗余的调用 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 1aebe6df07..9e0e721555 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -1722,6 +1722,21 @@ 必須為選擇性欄位提供還原序列化方法 + + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + + + + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method + + + Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides when subclassing 'Stream' + + Remove redundant call 移除冗餘的呼叫 diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs new file mode 100644 index 0000000000..16b45ae339 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs @@ -0,0 +1,299 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Test.Utilities; +using Xunit; + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.ProvideStreamMemoryBasedAsyncOverrides, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.ProvideStreamMemoryBasedAsyncOverrides, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class ProvideStreamMemoryBasedAsyncOverridesTests + { + #region Reports Diagnostic + [Fact] + public async Task ReadAsyncArray_NoReadAsyncMemory_ReportsDiagnostic_CS() + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class {{|#0:FooStream|}} : Stream + {{ + {CSAbstractMembers} + {CSReadAsyncArray} + }} +}}"; + + var diagnostic = VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("FooStream", CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory); + await new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task ReadAsyncArray_NoReadAsyncMemory_ReportsDiagnostic_VB() + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class {{|#0:FooStream|}} : Inherits Stream + {VBAbstractMembers} + {VBReadAsyncArray} + End Class +End Namespace"; + + var diagnostic = VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("FooStream", VBDisplayReadAsyncArray, VBDisplayReadAsyncMemory); + await new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task WriteAsyncArray_NoWriteAsyncMemory_ReportsDiagnostic_CS() + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class {{|#0:BarStream|}} : Stream + {{ + {CSAbstractMembers} + {CSWriteAsyncArray} + }} +}}"; + + var diagnostic = VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("BarStream", CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory); + await new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + + [Fact] + public async Task WriteAsyncArray_NoWriteAsyncMemory_ReportsDiagnostic_VB() + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class {{|#0:BarStream|}} : Inherits Stream + {VBAbstractMembers} + {VBWriteAsyncArray} + End Class +End Namespace"; + + var diagnostic = VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("BarStream", VBDisplayWriteAsyncArray, VBDisplayWriteAsyncMemory); + await new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { diagnostic } + }.RunAsync(); + } + #endregion + + #region No Diagnostic + [Fact] + public async Task ReadAsyncArray_WithReadAsyncMemory_NoDiagnostic_CS() + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class BazStream : Stream + {{ + {CSAbstractMembers} + {CSReadAsyncArray} + {CSReadAsyncMemory} + }} +}}"; + + await new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + + [Fact] + public async Task ReadAsyncArray_WithReadAsyncMemory_NoDiagnostic_VB() + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class BazStream : Inherits Stream + {VBAbstractMembers} + {VBReadAsyncArray} + {VBReadAsyncMemory} + End Class +End Namespace"; + + await new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + + [Fact] + public async Task WriteAsyncArray_WithWriteAsyncMemory_NoDiagnostic_CS() + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class WhippleStream : Stream + {{ + {CSAbstractMembers} + {CSWriteAsyncArray} + {CSWriteAsyncMemory} + }} +}}"; + + await new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + + [Fact] + public async Task WriteAsyncArray_WithWriteAsyncMemory_NoDiagnostic_VB() + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class WhippleStream : Inherits Stream + {VBAbstractMembers} + {VBWriteAsyncArray} + {VBWriteAsyncMemory} + End Class +End Namespace"; + + await new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + #endregion + + #region Helpers + private const string CSUsings = @"using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks;"; + private const string CSAbstractMembers = @"public override void Flush() => throw null; + public override int Read(byte[] buffer, int offset, int count) => throw null; + public override long Seek(long offset, SeekOrigin origin) => throw null; + public override void SetLength(long value) => throw null; + public override void Write(byte[] buffer, int offset, int count) => throw null; + public override bool CanRead { get; } + public override bool CanSeek { get; } + public override bool CanWrite { get; } + public override long Length { get; } + public override long Position { get; set; }"; + private const string CSReadAsyncArray = @"public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) => throw null;"; + private const string CSReadAsyncMemory = @"public override ValueTask ReadAsync(Memory buffer, CancellationToken ct = default) => throw null;"; + private const string CSWriteAsyncArray = @"public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct) => throw null;"; + private const string CSWriteAsyncMemory = @"public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken ct = default) => throw null;"; + + private const string VBUsings = @"Imports System +Imports System.IO +Imports System.Threading +Imports System.Threading.Tasks"; + private const string VBAbstractMembers = @"Public Overrides ReadOnly Property CanRead As Boolean + Get + Throw New NotImplementedException() + End Get + End Property + Public Overrides ReadOnly Property CanSeek As Boolean + Get + Throw New NotImplementedException() + End Get + End Property + Public Overrides ReadOnly Property CanWrite As Boolean + Get + Throw New NotImplementedException() + End Get + End Property + Public Overrides ReadOnly Property Length As Long + Get + Throw New NotImplementedException() + End Get + End Property + Public Overrides Property Position As Long + Get + Throw New NotImplementedException() + End Get + Set(value As Long) + Throw New NotImplementedException() + End Set + End Property + Public Overrides Sub Flush() + Throw New NotImplementedException() + End Sub + Public Overrides Sub SetLength(value As Long) + Throw New NotImplementedException() + End Sub + Public Overrides Sub Write(buffer() As Byte, offset As Integer, count As Integer) + Throw New NotImplementedException() + End Sub + Public Overrides Function Read(buffer() As Byte, offset As Integer, count As Integer) As Integer + Throw New NotImplementedException() + End Function + Public Overrides Function Seek(offset As Long, origin As SeekOrigin) As Long + Throw New NotImplementedException() + End Function"; + private const string VBReadAsyncArray = @"Public Overrides Function ReadAsync(buffer() As Byte, offset As Integer, count As Integer, ct As CancellationToken) As Task(Of Integer) + Throw New NotImplementedException() + End Function"; + private const string VBReadAsyncMemory = @"Public Overrides Function ReadAsync(buffer As Memory(Of Byte), Optional ct As CancellationToken = Nothing) As ValueTask(Of Integer) + Throw New NotImplementedException() + End Function"; + private const string VBWriteAsyncArray = @"Public Overrides Function WriteAsync(buffer() As Byte, offset As Integer, count As Integer, ct As CancellationToken) As Task + Throw New NotImplementedException() + End Function"; + private const string VBWriteAsyncMemory = @"Public Overrides Function WriteAsync(buffer As ReadOnlyMemory(Of Byte), Optional ct As CancellationToken = Nothing) As ValueTask + Throw New NotImplementedException() + End Function"; + + private const string CSDisplayReadAsyncArray = @"Task Stream.ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)"; + private const string CSDisplayReadAsyncMemory = @"ValueTask Stream.ReadAsync(Memory buffer, CancellationToken cancellationToken = default(CancellationToken))"; + private const string CSDisplayWriteAsyncArray = @"Task Stream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)"; + private const string CSDisplayWriteAsyncMemory = @"ValueTask Stream.WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string VBDisplayReadAsyncArray = @"Function Stream.ReadAsync(buffer As Byte(), offset As Integer, count As Integer, cancellationToken As CancellationToken) As Task(Of Integer)"; + private const string VBDisplayReadAsyncMemory = @"Function Stream.ReadAsync(buffer As Memory(Of Byte), cancellationToken As CancellationToken = Nothing) As ValueTask(Of Integer)"; + private const string VBDisplayWriteAsyncArray = @"Function Stream.WriteAsync(buffer As Byte(), offset As Integer, count As Integer, cancellationToken As CancellationToken) As Task"; + private const string VBDisplayWriteAsyncMemory = @"Function Stream.WriteAsync(buffer As ReadOnlyMemory(Of Byte), cancellationToken As CancellationToken = Nothing) As ValueTask"; + + private static DiagnosticDescriptor Rule => ProvideStreamMemoryBasedAsyncOverrides.Rule; + #endregion + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 94d4940230..949c085eec 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1310 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1838 +Performance: HA, CA1800-CA1840 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5403 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2249 Naming: CA1700-CA1726 From fbc706dc244fc4734363e1b3834b0b93836a4e11 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sun, 17 Jan 2021 16:48:26 -0500 Subject: [PATCH 012/224] Added more tests --- .../Core/AnalyzerReleases.Unshipped.md | 2 +- .../MicrosoftNetCoreAnalyzersResources.resx | 4 +- .../ProvideStreamMemoryBasedAsyncOverrides.cs | 38 +- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 8 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 8 +- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 + .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 + src/NetAnalyzers/RulesMissingDocumentation.md | 1 + .../ProvideStreamMemoryAsyncOverridesTests.cs | 979 +++++++++++++++++- 20 files changed, 1066 insertions(+), 94 deletions(-) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 019a1608f5..75e06755e8 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -3,7 +3,7 @@ ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- -CA1840 | Performance | Info | ProvideStreamMemoryBasedAsyncOverrides, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840) +CA1840 | Performance | Warning | ProvideStreamMemoryBasedAsyncOverrides, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840) ### Removed Rules diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index d8942f4288..52984e8891 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1514,10 +1514,10 @@ The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs index abe21b860f..878d321214 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; -using System.Text; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; @@ -15,6 +12,11 @@ namespace Microsoft.NetCore.Analyzers.Runtime { + /// + /// CA1840: Reports a diagnostic if a class that directly subclasses overrides + /// and/or , + /// and does not override the corrasponding memory-based version. + /// [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] public sealed class ProvideStreamMemoryBasedAsyncOverrides : DiagnosticAnalyzer { @@ -29,7 +31,7 @@ public sealed class ProvideStreamMemoryBasedAsyncOverrides : DiagnosticAnalyzer s_localizableTitle, s_localizableMessage, DiagnosticCategory.Performance, - RuleLevel.IdeSuggestion, + RuleLevel.BuildWarning, s_localizableDescription, isPortedFxCopRule: false, isDataflowRule: false); @@ -59,13 +61,19 @@ void AnalyzeNamedType(SymbolAnalysisContext context) { var type = (INamedTypeSymbol)context.Symbol; + // We only report a diagnostic if the type directly subclasses stream. We don't report diagnostics + // if there are any bases in the middle. + if (!symbols.StreamType.Equals(type.BaseType, SymbolEqualityComparer.Default)) return; - IMethodSymbol? readAsyncArrayOverride = GetOverride(type, symbols.ReadAsyncArrayMethod); - IMethodSymbol? readAsyncMemoryOverride = GetOverride(type, symbols.ReadAsyncMemoryMethod); - IMethodSymbol? writeAsyncArrayOverride = GetOverride(type, symbols.WriteAsyncArrayMethod); - IMethodSymbol? writeAsyncMemoryOverride = GetOverride(type, symbols.WriteAsyncMemoryMethod); + IMethodSymbol? readAsyncArrayOverride = GetSymbolForOverridingMethod(type, symbols.ReadAsyncArrayMethod); + IMethodSymbol? readAsyncMemoryOverride = GetSymbolForOverridingMethod(type, symbols.ReadAsyncMemoryMethod); + IMethodSymbol? writeAsyncArrayOverride = GetSymbolForOverridingMethod(type, symbols.WriteAsyncArrayMethod); + IMethodSymbol? writeAsyncMemoryOverride = GetSymbolForOverridingMethod(type, symbols.WriteAsyncMemoryMethod); + + // For both ReadAsync and WriteAsync, if the array-based form is overridden and the memory-based + // form is not, we report a diagnostic. We report separate diagnostics for ReadAsync and WriteAsync. if (readAsyncArrayOverride is not null && readAsyncMemoryOverride is null) { @@ -84,9 +92,13 @@ static Diagnostic CreateDiagnostic(INamedTypeSymbol violatingType, IMethodSymbol { RoslynDebug.Assert(arrayBasedOverride.OverriddenMethod is not null); - var location = violatingType.Locations.Single(x => x.SourceTree == arrayBasedOverride.Locations[0].SourceTree); + // We want to underline the name of the violating type in the class declaration. If the violating type + // is a partial class, we underline all partial declarations. + return Diagnostic.Create( - Rule, location, + Rule, + violatingType.Locations[0], + violatingType.Locations.Skip(1), violatingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), arrayBasedOverride.OverriddenMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), memoryBasedMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); @@ -147,8 +159,12 @@ static bool IsMatch(IMethodSymbol method, ITypeSymbol[] argumentTypes) } } - private static IMethodSymbol? GetOverride(ITypeSymbol derivedType, IMethodSymbol overriddenMethod) + // If the specified type overrides the specified method on its immediate base class, returns the method symbol representing the + // overriding method. Returns null if the specified type does not override the specified method. + private static IMethodSymbol? GetSymbolForOverridingMethod(ITypeSymbol derivedType, IMethodSymbol overriddenMethod) { + RoslynDebug.Assert(derivedType.BaseType.Equals(overriddenMethod.ContainingType, SymbolEqualityComparer.Default)); + return derivedType.GetMembers(overriddenMethod.Name) .SingleOrDefault(x => { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index ebe70f5fe6..4ea13a7af1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 6917a1ff08..e7ca597a28 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 6b7771206b..53447ec222 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index bcb0917a63..b9ab6c37e6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 5745cfbba1..ca8748f242 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index e3f892ccd0..7e973d459d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index e58b87fd9b..f2c2e5cbc2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 7a77f6bf51..257cf4073e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 11cb90a1bc..d10ad4d4da 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 1f528617dd..a66f132034 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 57a415bbdc..a97dab61e3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 383f8fa001..340cc04f65 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 9e0e721555..dbfebf63a5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -1728,13 +1728,13 @@ - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. - '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}'. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. + '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. 0 = type that subclasses Stream directly, 1 = array-based method, 2 = memory-based method - Provide memory-based overrides when subclassing 'Stream' - Provide memory-based overrides when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' + Provide memory-based overrides of async methods when subclassing 'Stream' diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index fa5d976400..c2a2f2d7a2 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1296,6 +1296,18 @@ Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in |CodeFix|False| --- +## [CA1840](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840): Provide memory-based overrides of async methods when subclassing 'Stream' + +The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Warning| +|CodeFix|False| +--- + ## [CA2000](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope 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. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 72c37b9580..a4643db843 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -2442,6 +2442,26 @@ ] } }, + "CA1840": { + "id": "CA1840", + "shortDescription": "Provide memory-based overrides of async methods when subclassing 'Stream'", + "fullDescription": "The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'.", + "defaultLevel": "warning", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "ProvideStreamMemoryBasedAsyncOverrides", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 75e0f77588..0c3415129a 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,3 +2,4 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| +CA1840 | | Provide memory-based overrides of async methods when subclassing 'Stream' | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs index 16b45ae339..84410711ee 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -21,7 +22,7 @@ public class ProvideStreamMemoryBasedAsyncOverridesTests { #region Reports Diagnostic [Fact] - public async Task ReadAsyncArray_NoReadAsyncMemory_ReportsDiagnostic_CS() + public Task ReadAsyncArray_NoReadAsyncMemory_ReportsDiagnostic_CS() { string code = $@" {CSUsings} @@ -37,16 +38,17 @@ public class {{|#0:FooStream|}} : Stream var diagnostic = VerifyCS.Diagnostic(Rule) .WithLocation(0) .WithArguments("FooStream", CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory); - await new VerifyCS.Test + var test = new VerifyCS.Test { TestCode = code, ReferenceAssemblies = ReferenceAssemblies.Net.Net50, ExpectedDiagnostics = { diagnostic } - }.RunAsync(); + }; + return test.RunAsync(); } [Fact] - public async Task ReadAsyncArray_NoReadAsyncMemory_ReportsDiagnostic_VB() + public Task ReadAsyncArray_NoReadAsyncMemory_ReportsDiagnostic_VB() { string code = $@" {VBUsings} @@ -60,16 +62,17 @@ End Class var diagnostic = VerifyVB.Diagnostic(Rule) .WithLocation(0) .WithArguments("FooStream", VBDisplayReadAsyncArray, VBDisplayReadAsyncMemory); - await new VerifyVB.Test + var test = new VerifyVB.Test { TestCode = code, ReferenceAssemblies = ReferenceAssemblies.Net.Net50, ExpectedDiagnostics = { diagnostic } - }.RunAsync(); + }; + return test.RunAsync(); } [Fact] - public async Task WriteAsyncArray_NoWriteAsyncMemory_ReportsDiagnostic_CS() + public Task WriteAsyncArray_NoWriteAsyncMemory_ReportsDiagnostic_CS() { string code = $@" {CSUsings} @@ -85,16 +88,17 @@ public class {{|#0:BarStream|}} : Stream var diagnostic = VerifyCS.Diagnostic(Rule) .WithLocation(0) .WithArguments("BarStream", CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory); - await new VerifyCS.Test + var test = new VerifyCS.Test { TestCode = code, ReferenceAssemblies = ReferenceAssemblies.Net.Net50, ExpectedDiagnostics = { diagnostic } - }.RunAsync(); + }; + return test.RunAsync(); } [Fact] - public async Task WriteAsyncArray_NoWriteAsyncMemory_ReportsDiagnostic_VB() + public Task WriteAsyncArray_NoWriteAsyncMemory_ReportsDiagnostic_VB() { string code = $@" {VBUsings} @@ -108,18 +112,530 @@ End Class var diagnostic = VerifyVB.Diagnostic(Rule) .WithLocation(0) .WithArguments("BarStream", VBDisplayWriteAsyncArray, VBDisplayWriteAsyncMemory); - await new VerifyVB.Test + var test = new VerifyVB.Test { TestCode = code, ReferenceAssemblies = ReferenceAssemblies.Net.Net50, ExpectedDiagnostics = { diagnostic } - }.RunAsync(); + }; + return test.RunAsync(); + } + + [Fact] + public Task BothArrayOverrides_MissingAllMemoryOverrides_ReportsMultipleBiagnostics_CS() + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class {{|#0:River|}} : Stream + {{ + {CSAbstractMembers} + {CSReadAsyncArray} + {CSWriteAsyncArray} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("River", CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory), + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("River", CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory) + } + }; + return test.RunAsync(); + } + + [Fact] + public Task BothArrayOverrides_MissingAllMemoryOverrides_ReportsMultipleDiagnostics_VB() + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class {{|#0:River|}} : Inherits Stream + {VBAbstractMembers} + {VBReadAsyncArray} + {VBWriteAsyncArray} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("River", VBDisplayWriteAsyncArray, VBDisplayWriteAsyncMemory), + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("River", VBDisplayReadAsyncArray, VBDisplayReadAsyncMemory) + + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(CSReadAsyncArray, CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory)] + [InlineData(CSWriteAsyncArray, CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory)] + public Task SingleArrayOverride_MultiplePartialsInSameFile_ReportsAllLocations_CS(string arrayMethod, string displayArrayMethod, string displayMemoryMethod) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public partial class {{|#0:River|}} : Stream + {{ + {CSAbstractMembers} + }} +}} + +namespace Testopolis +{{ + partial class {{|#1:River|}} + {{ + {arrayMethod} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithArguments("River", displayArrayMethod, displayMemoryMethod), + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(VBReadAsyncArray, VBDisplayReadAsyncArray, VBDisplayReadAsyncMemory)] + [InlineData(VBWriteAsyncArray, VBDisplayWriteAsyncArray, VBDisplayWriteAsyncMemory)] + public Task SingleArrayOverride_MultiplePartialsInSameFile_ReportsAllLocations_VB(string arrayMethod, string displayArrayMethod, string displayMemoryMethod) + { + string code = $@" +{VBUsings} +Namespace Testopolis + Partial Public Class {{|#0:River|}} : Inherits Stream + {VBAbstractMembers} + {arrayMethod} + End Class +End Namespace + +Namespace Testopolis + Partial Class {{|#1:River|}} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithArguments("River", displayArrayMethod, displayMemoryMethod) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(CSReadAsyncArray, CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory)] + [InlineData(CSWriteAsyncArray, CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory)] + public Task SingleArrayOverride_MultiplePartialsInSeparateFiles_ReportsAllLocations_CS(string arrayMethod, string displayArrayMethod, string displayMemoryMethod) + { + string fooSource = $@" +{CSUsings} +namespace Testopolis +{{ + public partial class {{|#0:River|}} : Stream + {{ + {CSAbstractMembers} + }} +}}"; + string barSource = $@" +{CSUsings} +namespace Testopolis +{{ + partial class {{|#1:River|}} + {{ + {arrayMethod} + }} +}}"; + string bazSource = $@" +{CSUsings} +namespace Testopolis +{{ + partial class {{|#2:River|}} + {{ + }} +}}"; + var test = new VerifyCS.Test + { + TestState = { Sources = { fooSource, barSource, bazSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", displayArrayMethod, displayMemoryMethod) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(VBReadAsyncArray, VBDisplayReadAsyncArray, VBDisplayReadAsyncMemory)] + [InlineData(VBWriteAsyncArray, VBDisplayWriteAsyncArray, VBDisplayWriteAsyncMemory)] + public Task SingleArrayOverride_MultiplePartialsInSeparateFiles_ReportsAllLocations_VB(string arrayMethod, string displayArrayMethod, string displayMemoryMethod) + { + string fooSource = $@" +{VBUsings} +Namespace Testopolis + Partial Public Class {{|#0:River|}} : Inherits Stream + {VBAbstractMembers} + End Class +End Namespace"; + string barSource = $@" +{VBUsings} +Namespace Testopolis + Partial Class {{|#1:River|}} + {arrayMethod} + End Class +End Namespace"; + string bazSource = $@" +{VBUsings} +Namespace Testopolis + Partial Class {{|#2:River|}} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestState = { Sources = { fooSource, barSource, bazSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", displayArrayMethod, displayMemoryMethod) + } + }; + return test.RunAsync(); + } + + [Fact] + public Task BothArrayOverrides_MultiplePartialsInSameFile_ReportsAllLocations_CS() + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public partial class {{|#0:River|}} : Stream + {{ + {CSAbstractMembers} + {CSReadAsyncArray} + }} +}} + +namespace Testopolis +{{ + partial class {{|#1:River|}} + {{ + }} +}} + +namespace Testopolis +{{ + partial class {{|#2:River|}} + {{ + {CSWriteAsyncArray} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory), + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory) + } + }; + return test.RunAsync(); + } + + [Fact] + public Task BothArrayOverrides_MultiplePartialsInSameFile_REportsAllLocations_VB() + { + string code = $@" +{VBUsings} +Namespace Testopolis + Partial Public Class {{|#0:River|}} : Inherits Stream + {VBAbstractMembers} + {VBReadAsyncArray} + End Class +End Namespace + +Namespace Testopolis + Partial Class {{|#1:River|}} + End Class +End Namespace + +Namespace Testopolis + Partial Class {{|#2:River|}} + {VBWriteAsyncArray} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", VBDisplayReadAsyncArray, VBDisplayReadAsyncMemory), + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", VBDisplayWriteAsyncArray, VBDisplayWriteAsyncMemory) + } + }; + return test.RunAsync(); + } + + [Fact] + public Task BothArrayOverrides_MultiplePartialsInSeparateFiles_ReportsAllLocations_CS() + { + string fooSource = $@" +{CSUsings} +namespace Testopolis +{{ + public partial class {{|#0:River|}} : Stream + {{ + {CSAbstractMembers} + {CSReadAsyncArray} + }} +}}"; + string barSource = $@" +{CSUsings} +namespace Testopolis +{{ + partial class {{|#1:River|}} + {{ + }} +}}"; + string bazSource = $@" +{CSUsings} +namespace Testopolis +{{ + partial class {{|#2:River|}} + {{ + {CSWriteAsyncArray} + }} +}}"; + + var test = new VerifyCS.Test + { + TestState = { Sources = { fooSource, barSource, bazSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory), + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory) + } + }; + return test.RunAsync(); + } + + [Fact] + public Task BothArrayOverrides_MultiplePartialsInSeparateFiles_ReportsAllLocations_VB() + { + string fooSource = $@" +{VBUsings} +Namespace Testopolis + Partial Public Class {{|#0:River|}} : Inherits Stream + {VBAbstractMembers} + {VBReadAsyncArray} + End Class +End Namespace"; + string barSource = $@" +{VBUsings} +Namespace Testopolis + Partial Class {{|#1:River|}} + End Class +End Namespace"; + string bazSource = $@" +{VBUsings} +Namespace Testopolis + Partial Class {{|#2:River|}} + {VBWriteAsyncArray} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestState = { Sources = { fooSource, barSource, bazSource } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", VBDisplayReadAsyncArray, VBDisplayReadAsyncMemory), + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithLocation(1) + .WithLocation(2) + .WithArguments("River", VBDisplayWriteAsyncArray, VBDisplayWriteAsyncMemory) + } + }; + return test.RunAsync(); + } + + // This test has no VB counterpart because in Visual Basic it is illegal to override one overload + // of a base-class method while implicitly hiding another overload. + [Theory] + [InlineData(CSReadAsyncArray, CSHideReadAsyncMemory, CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory)] + [InlineData(CSWriteAsyncArray, CSHideWriteAsyncMemory, CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory)] + public Task WhenMemoryMethodNotDeclaredOverride_ReportsDiagnostic(string arrayMethod, string memoryMethod, string displayArrayMethod, string displayMemoryMethod) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class {{|#0:River|}} : Stream + {{ + {CSAbstractMembers} + {arrayMethod} +#pragma warning disable {CSMemberHidesBaseRuleId} + {memoryMethod} +#pragma warning restore {CSMemberHidesBaseRuleId} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("River", displayArrayMethod, displayMemoryMethod) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(CSReadAsyncArray, CSHideExplicitReadAsyncMemory, CSDisplayReadAsyncArray, CSDisplayReadAsyncMemory)] + [InlineData(CSWriteAsyncArray, CSHideExplicitWriteAsyncMemory, CSDisplayWriteAsyncArray, CSDisplayWriteAsyncMemory)] + public Task WhenMemoryMethodDeclaredNew_ReportsDiagnostic_CS(string arrayMethod, string memoryMethod, string displayArrayMethod, string displayMemoryMethod) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class {{|#0:River|}} : Stream + {{ + {CSAbstractMembers} + {arrayMethod} + {memoryMethod} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("River", displayArrayMethod, displayMemoryMethod) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(VBReadAsyncArray, VBHideExplicitReadAsyncMemory, VBDisplayReadAsyncArray, VBDisplayReadAsyncMemory)] + [InlineData(VBWriteAsyncArray, VBHideExplicitWriteAsyncMemory, VBDisplayWriteAsyncArray, VBDisplayWriteAsyncMemory)] + public Task WhenMemoryMethodDeclaredNew_ReportsDiagnostic_VB(string arrayMethod, string memoryMethod, string displayArrayMethod, string displayMemoryMethod) + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class {{|#0:River|}} : Inherits Stream + {VBAbstractMembers} + {arrayMethod} + {memoryMethod} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + VerifyVB.Diagnostic(Rule) + .WithLocation(0) + .WithArguments("River", displayArrayMethod, displayMemoryMethod) + } + }; + return test.RunAsync(); } #endregion #region No Diagnostic [Fact] - public async Task ReadAsyncArray_WithReadAsyncMemory_NoDiagnostic_CS() + public Task ReadAsyncArray_WithReadAsyncMemory_NoDiagnostic_CS() { string code = $@" {CSUsings} @@ -133,15 +649,16 @@ public class BazStream : Stream }} }}"; - await new VerifyCS.Test + var test = new VerifyCS.Test { TestCode = code, ReferenceAssemblies = ReferenceAssemblies.Net.Net50 - }.RunAsync(); + }; + return test.RunAsync(); } [Fact] - public async Task ReadAsyncArray_WithReadAsyncMemory_NoDiagnostic_VB() + public Task ReadAsyncArray_WithReadAsyncMemory_NoDiagnostic_VB() { string code = $@" {VBUsings} @@ -153,15 +670,16 @@ Namespace Testopolis End Class End Namespace"; - await new VerifyVB.Test + var test = new VerifyVB.Test { TestCode = code, ReferenceAssemblies = ReferenceAssemblies.Net.Net50 - }.RunAsync(); + }; + return test.RunAsync(); } [Fact] - public async Task WriteAsyncArray_WithWriteAsyncMemory_NoDiagnostic_CS() + public Task WriteAsyncArray_WithWriteAsyncMemory_NoDiagnostic_CS() { string code = $@" {CSUsings} @@ -175,15 +693,16 @@ public class WhippleStream : Stream }} }}"; - await new VerifyCS.Test + var test = new VerifyCS.Test { TestCode = code, ReferenceAssemblies = ReferenceAssemblies.Net.Net50 - }.RunAsync(); + }; + return test.RunAsync(); } [Fact] - public async Task WriteAsyncArray_WithWriteAsyncMemory_NoDiagnostic_VB() + public Task WriteAsyncArray_WithWriteAsyncMemory_NoDiagnostic_VB() { string code = $@" {VBUsings} @@ -195,11 +714,382 @@ Namespace Testopolis End Class End Namespace"; - await new VerifyVB.Test + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task ReadAsyncMemory_WithoutReadAsyncArray_NoDiagnostic_CS() + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class River : Stream + {{ + {CSAbstractMembers} + {CSReadAsyncMemory} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task ReadAsyncMemory_WithoutReadAsyncArray_NoDiagnostic_VB() + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class River : Inherits Stream + {VBAbstractMembers} + {VBReadAsyncMemory} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task WriteAsyncMemory_WithoutWriteAsyncArray_NoDiagnostic_CS() + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class River : Stream + {{ + {CSAbstractMembers} + {CSWriteAsyncMemory} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task WriteAsyncMemory_WithoutWriteAsyncArray_NoDiagnostic_VB() + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class River : Inherits Stream + {VBAbstractMembers} + {VBWriteAsyncMemory} + End Class +End Namespace"; + + var test = new VerifyVB.Test { TestCode = code, ReferenceAssemblies = ReferenceAssemblies.Net.Net50 - }.RunAsync(); + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(CSReadAsyncArray)] + [InlineData(CSWriteAsyncArray)] + public Task WhenStreamIsGrandBase_andBaseDoesNotOverrideArrayMethod_NoDiagnostic_CS(string arrayMethodDefinition) + { + string @base = $@" +{CSUsings} +namespace Testopolis +{{ + public class BaseStream : Stream + {{ + {CSAbstractMembers} + }} +}}"; + string derived = $@" +{CSUsings} +namespace Testopolis +{{ + public class DerivedStream : BaseStream + {{ + {arrayMethodDefinition} + }} +}}"; + + var test = new VerifyCS.Test + { + TestState = { Sources = { @base, derived } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(VBReadAsyncArray)] + [InlineData(VBWriteAsyncArray)] + public Task WhenStreamIsGrandBase_andBaseDoesNotOverrideArrayMethod_NoDiagnostic_VB(string arrayMethodDefinition) + { + string @base = $@" +{VBUsings} +Namespace Testopolis + Public Class BaseStream : Inherits Stream + {VBAbstractMembers} + End Class +End Namespace"; + string derived = $@" +{VBUsings} +Namespace Testopolis + Public Class DerivedStream : Inherits BaseStream + {arrayMethodDefinition} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestState = { Sources = { @base, derived } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(CSReadAsyncArray)] + [InlineData(CSWriteAsyncArray)] + public Task WhenStreamIsGrandBase_andBaseOverridesArrayMethod_NoDiagnostic_CS(string arrayMethodDefinition) + { + string @base = $@" +{CSUsings} +namespace Testopolis +{{ +#pragma warning disable {RuleId} + public class BaseStream : Stream +#pragma warning restore {RuleId} + {{ + {CSAbstractMembers} + {arrayMethodDefinition} + }} +}}"; + string derived = $@" +{CSUsings} +namespace Testopolis +{{ + public class DerivedStream : BaseStream + {{ + }} +}}"; + + var test = new VerifyCS.Test + { + TestState = { Sources = { @base, derived } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(VBReadAsyncArray)] + [InlineData(VBWriteAsyncArray)] + public Task WhenStreamIsGrandBase_andBaseOverridesArray_NoDiagnostic_VB(string arrayMethodDefinition) + { + string @base = $@" +{VBUsings} +Namespace Testopolis +#Disable Warning {RuleId} + Public Class BaseStream : Inherits Stream +#Enable Warning {RuleId} + {VBAbstractMembers} + {arrayMethodDefinition} + End Class +End Namespace"; + string derived = $@" +{VBUsings} +Namespace Testopolis + Public Class DerivedStream : Inherits BaseStream + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestState = { Sources = { @base, derived } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(CSReadAsyncArray)] + [InlineData(CSWriteAsyncArray)] + public Task WhenStreamIsGrandBase_andBothBaseAndDerivedOverrideArrayMethod_NoDiagnostic_CS(string arrayMethodDefinition) + { + string @base = $@" +{CSUsings} +namespace Testopolis +{{ +#pragma warning disable {RuleId} + public class BaseStream : Stream +#pragma warning restore {RuleId} + {{ + {CSAbstractMembers} + {arrayMethodDefinition} + }} +}}"; + string derived = $@" +{CSUsings} +namespace Testopolis +{{ + public class DerivedStream : BaseStream + {{ + {arrayMethodDefinition} + }} +}}"; + + var test = new VerifyCS.Test + { + TestState = { Sources = { @base, derived } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(VBReadAsyncArray)] + [InlineData(VBWriteAsyncArray)] + public Task WhenStreamIsGrandBase_andBothBaseAndDerivedOverrideArrayMethod_NoDiagnostic_VB(string arrayMethodDefinition) + { + string @base = $@" +{VBUsings} +Namespace Testopolis +#Disable Warning {RuleId} + Public Class BaseStream : Inherits Stream +#Enable Warning {RuleId} + {VBAbstractMembers} + {arrayMethodDefinition} + End Class +End Namespace"; + string derived = $@" +{VBUsings} +Namespace Testopolis + Public Class DerivedStream : Inherits BaseStream + {arrayMethodDefinition} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestState = { Sources = { @base, derived } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(CSHideReadAsyncArray)] + [InlineData(CSHideWriteAsyncArray)] + public Task WhenArrayMethodNotDeclaredOverride_NoDiagnostic_CS(string arrayMethod) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class River : Stream + {{ + {CSAbstractMembers} +#pragma warning disable {CSMemberHidesBaseRuleId} + {arrayMethod} +#pragma warning restore {CSMemberHidesBaseRuleId} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(VBHideReadAsyncArray)] + [InlineData(VBHideWriteAsyncArray)] + public Task WhenArrayMethodNotDeclaredOverride_NoDiagnostic_VB(string arrayMethod) + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class River : Inherits Stream + {VBAbstractMembers} +#Disable Warning {VBMemberHidesBaseRuleId} + {arrayMethod} +#Enable Warning {VBMemberHidesBaseRuleId} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(CSHideExplicitReadAsyncArray)] + [InlineData(CSHideExplicitWriteAsyncArray)] + public Task WhenArrayMethodDeclaredNew_NoDiagnostic_CS(string arrayMethod) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class River : Stream + {{ + {CSAbstractMembers} + {arrayMethod} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(VBHideExplicitReadAsyncArray)] + [InlineData(VBHideExplicitWriteAsyncArray)] + public Task WhenArrayMethodDeclaredNew_NoDiagnostic_VB(string arrayMethod) + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class River : Inherits Stream + {VBAbstractMembers} + {arrayMethod} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); } #endregion @@ -218,10 +1108,10 @@ End Class public override bool CanWrite { get; } public override long Length { get; } public override long Position { get; set; }"; - private const string CSReadAsyncArray = @"public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) => throw null;"; - private const string CSReadAsyncMemory = @"public override ValueTask ReadAsync(Memory buffer, CancellationToken ct = default) => throw null;"; - private const string CSWriteAsyncArray = @"public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct) => throw null;"; - private const string CSWriteAsyncMemory = @"public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken ct = default) => throw null;"; + private const string CSReadAsyncArray = @"public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw null;"; + private const string CSReadAsyncMemory = @"public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => throw null;"; + private const string CSWriteAsyncArray = @"public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw null;"; + private const string CSWriteAsyncMemory = @"public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw null;"; private const string VBUsings = @"Imports System Imports System.IO @@ -293,7 +1183,40 @@ Throw New NotImplementedException() private const string VBDisplayWriteAsyncArray = @"Function Stream.WriteAsync(buffer As Byte(), offset As Integer, count As Integer, cancellationToken As CancellationToken) As Task"; private const string VBDisplayWriteAsyncMemory = @"Function Stream.WriteAsync(buffer As ReadOnlyMemory(Of Byte), cancellationToken As CancellationToken = Nothing) As ValueTask"; + private const string CSHideReadAsyncArray = @"public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw null;"; + private const string CSHideReadAsyncMemory = @"public ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => throw null;"; + private const string CSHideWriteAsyncArray = @"public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw null;"; + private const string CSHideWriteAsyncMemory = @"public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw null;"; + + private const string VBHideReadAsyncArray = @"Public Function ReadAsync(buffer() As Byte, offset As Integer, count As Integer, cancellationToken As CancellationToken) As Task(Of Integer) + Throw New NotImplementedException() + End Function"; + private const string VBHideWriteAsyncArray = @"Public Function WriteAsync(buffer As Byte, offset As Integer, count As Integer, cancellationToken As CancellationToken) As Task + Throw New NotImplementedException() + End Function"; + + private const string CSHideExplicitReadAsyncArray = @"public new Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw null;"; + private const string CSHideExplicitReadAsyncMemory = @"public new ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => throw null;"; + private const string CSHideExplicitWriteAsyncArray = @"public new Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw null;"; + private const string CSHideExplicitWriteAsyncMemory = @"public new ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw null;"; + + private const string VBHideExplicitReadAsyncArray = @"Public Overloads Function ReadAsync(buffer() As Byte, offset As Integer, count As Integer, cancellationToken As CancellationToken) As Task(Of Integer) + Throw New NotImplementedException() + End Function"; + private const string VBHideExplicitReadAsyncMemory = @"Public Overloads Function ReadAsync(buffer As Memory(Of Byte), Optional cancellationToken As CancellationToken = Nothing) As ValueTask(Of Integer) + Throw New NotImplementedException() + End Function"; + private const string VBHideExplicitWriteAsyncArray = @"Public Overloads Function WriteAsync(buffer As Byte, offset As Integer, count As Integer, cancellationToken As CancellationToken) As Task + Throw New NotImplementedException() + End Function"; + private const string VBHideExplicitWriteAsyncMemory = @"Public Overloads Function WriteAsync(buffer As ReadOnlyMemory(Of Byte), Optional cancellationToken As CancellationToken = Nothing) As ValueTask + Throw New NotImplementedException() + End Function"; + private static DiagnosticDescriptor Rule => ProvideStreamMemoryBasedAsyncOverrides.Rule; + private static string RuleId => ProvideStreamMemoryBasedAsyncOverrides.RuleId; + private const string CSMemberHidesBaseRuleId = "CS0114"; + private const string VBMemberHidesBaseRuleId = "BC40005"; #endregion } } From 8b9cf92a26045924ead7fe838e2eb8f469bf2c0e Mon Sep 17 00:00:00 2001 From: NewellClark Date: Thu, 21 Jan 2021 12:39:41 -0500 Subject: [PATCH 013/224] Add ProvideStreamMemoryBasedAsyncOverrides analyzer Fix issue #33789 --- src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md | 2 +- .../Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 75e06755e8..019a1608f5 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -3,7 +3,7 @@ ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- -CA1840 | Performance | Warning | ProvideStreamMemoryBasedAsyncOverrides, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840) +CA1840 | Performance | Info | ProvideStreamMemoryBasedAsyncOverrides, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840) ### Removed Rules diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs index 878d321214..21eaeb88e5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs @@ -31,7 +31,7 @@ public sealed class ProvideStreamMemoryBasedAsyncOverrides : DiagnosticAnalyzer s_localizableTitle, s_localizableMessage, DiagnosticCategory.Performance, - RuleLevel.BuildWarning, + RuleLevel.IdeSuggestion, s_localizableDescription, isPortedFxCopRule: false, isDataflowRule: false); From fe1d3b90780fa382bb584f38bd6018752aa237bb Mon Sep 17 00:00:00 2001 From: NewellClark Date: Thu, 21 Jan 2021 14:27:18 -0500 Subject: [PATCH 014/224] Forgot to MSBuild --- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md | 2 +- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index c2a2f2d7a2..0cd7f9fa21 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1304,7 +1304,7 @@ The default implementation of '{2}' delegates to '{1}', which is innefficient as |-|-| |Category|Performance| |Enabled|True| -|Severity|Warning| +|Severity|Info| |CodeFix|False| --- diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index a4643db843..0a4c80777d 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -2446,7 +2446,7 @@ "id": "CA1840", "shortDescription": "Provide memory-based overrides of async methods when subclassing 'Stream'", "fullDescription": "The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'.", - "defaultLevel": "warning", + "defaultLevel": "note", "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840", "properties": { "category": "Performance", From 4f667a72ff3322c800d2680e676cfe397bcb885c Mon Sep 17 00:00:00 2001 From: Newell Clark Date: Fri, 22 Jan 2021 10:29:24 -0500 Subject: [PATCH 015/224] Update src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs Co-authored-by: Youssef Victor <31348972+Youssef1313@users.noreply.github.com> --- .../Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs index 21eaeb88e5..4e3fc194ca 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs @@ -38,8 +38,8 @@ public sealed class ProvideStreamMemoryBasedAsyncOverrides : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - private const string ReadAsyncName = "ReadAsync"; - private const string WriteAsyncName = "WriteAsync"; + private const string ReadAsyncName = nameof(System.IO.Stream.ReadAsync); + private const string WriteAsyncName = nameof(System.IO.Stream.WriteAsync); public override void Initialize(AnalysisContext context) { From 8940025084fb2faa50fdac5fa5d8cda56b6c7af0 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Fri, 22 Jan 2021 14:15:27 -0500 Subject: [PATCH 016/224] Fix crash bug on illegal code If there are multiple definitions of the same overridden method, the analyzer can crash due to uses of `SingleOrDefault` instead of `FirstOrDefault`. To fix, I refactored `GetOverload` and `GetSymbolForOverridingMethod` into immutable-array-returning methods, and simply used `FirstOrDefault` or `SingleOrDefault` at each call-site as appropriate. --- .../ProvideStreamMemoryBasedAsyncOverrides.cs | 45 ++-- .../ProvideStreamMemoryAsyncOverridesTests.cs | 254 ++++++++++++++++++ 2 files changed, 282 insertions(+), 17 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs index 4e3fc194ca..b207209358 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs @@ -67,10 +67,13 @@ void AnalyzeNamedType(SymbolAnalysisContext context) if (!symbols.StreamType.Equals(type.BaseType, SymbolEqualityComparer.Default)) return; - IMethodSymbol? readAsyncArrayOverride = GetSymbolForOverridingMethod(type, symbols.ReadAsyncArrayMethod); - IMethodSymbol? readAsyncMemoryOverride = GetSymbolForOverridingMethod(type, symbols.ReadAsyncMemoryMethod); - IMethodSymbol? writeAsyncArrayOverride = GetSymbolForOverridingMethod(type, symbols.WriteAsyncArrayMethod); - IMethodSymbol? writeAsyncMemoryOverride = GetSymbolForOverridingMethod(type, symbols.WriteAsyncMemoryMethod); + // We use 'FirstOrDefault' because there could be multiple overrides for the same method if + // there are compiler errors. + + IMethodSymbol? readAsyncArrayOverride = GetOverridingMethodSymbols(type, symbols.ReadAsyncArrayMethod).FirstOrDefault(); + IMethodSymbol? readAsyncMemoryOverride = GetOverridingMethodSymbols(type, symbols.ReadAsyncMemoryMethod).FirstOrDefault(); + IMethodSymbol? writeAsyncArrayOverride = GetOverridingMethodSymbols(type, symbols.WriteAsyncArrayMethod).FirstOrDefault(); + IMethodSymbol? writeAsyncMemoryOverride = GetOverridingMethodSymbols(type, symbols.WriteAsyncMemoryMethod).FirstOrDefault(); // For both ReadAsync and WriteAsync, if the array-based form is overridden and the memory-based // form is not, we report a diagnostic. We report separate diagnostics for ReadAsync and WriteAsync. @@ -121,10 +124,12 @@ private static bool TryGetRequiredSymbols(Compilation compilation, out RequiredS return false; } - var readAsyncArrayMethod = GetOverload(streamType, ReadAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType); - var readAsyncMemoryMethod = GetOverload(streamType, ReadAsyncName, memoryOfByteType, cancellationTokenType); - var writeAsyncArrayMethod = GetOverload(streamType, WriteAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType); - var writeAsyncMemoryMethod = GetOverload(streamType, WriteAsyncName, readOnlyMemoryOfByteType, cancellationTokenType); + // We know that there are no compiler errors on streamType, so we use 'SingleOrDefault'. + + var readAsyncArrayMethod = GetOverloads(streamType, ReadAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType).SingleOrDefault(); + var readAsyncMemoryMethod = GetOverloads(streamType, ReadAsyncName, memoryOfByteType, cancellationTokenType).SingleOrDefault(); + var writeAsyncArrayMethod = GetOverloads(streamType, WriteAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType).SingleOrDefault(); + var writeAsyncMemoryMethod = GetOverloads(streamType, WriteAsyncName, readOnlyMemoryOfByteType, cancellationTokenType).SingleOrDefault(); if (readAsyncArrayMethod is null || readAsyncMemoryMethod is null || writeAsyncArrayMethod is null || writeAsyncMemoryMethod is null) { @@ -139,10 +144,12 @@ private static bool TryGetRequiredSymbols(Compilation compilation, out RequiredS return true; } - private static IMethodSymbol? GetOverload(ITypeSymbol containingType, string methodName, params ITypeSymbol[] argumentTypes) + // There could be more than one overload with an exact match if there are compiler errors. + private static ImmutableArray GetOverloads(ITypeSymbol containingType, string methodName, params ITypeSymbol[] argumentTypes) { return containingType.GetMembers(methodName) - .SingleOrDefault(symbol => symbol is IMethodSymbol m && IsMatch(m, argumentTypes)) as IMethodSymbol; + .OfType() + .WhereAsArray(m => IsMatch(m, argumentTypes)); static bool IsMatch(IMethodSymbol method, ITypeSymbol[] argumentTypes) { @@ -159,17 +166,21 @@ static bool IsMatch(IMethodSymbol method, ITypeSymbol[] argumentTypes) } } - // If the specified type overrides the specified method on its immediate base class, returns the method symbol representing the - // overriding method. Returns null if the specified type does not override the specified method. - private static IMethodSymbol? GetSymbolForOverridingMethod(ITypeSymbol derivedType, IMethodSymbol overriddenMethod) + /// + /// If overrides on its immediate base class, returns the + /// for the overriding method. Returns null if does not override . + /// + /// The type that may have overridden a method on its immediate base-class. + /// + /// The for the method that overrides , or null if + /// is not overridden. + private static ImmutableArray GetOverridingMethodSymbols(ITypeSymbol derivedType, IMethodSymbol overriddenMethod) { RoslynDebug.Assert(derivedType.BaseType.Equals(overriddenMethod.ContainingType, SymbolEqualityComparer.Default)); return derivedType.GetMembers(overriddenMethod.Name) - .SingleOrDefault(x => - { - return x.IsOverride && overriddenMethod.Equals(x.GetOverriddenMember(), SymbolEqualityComparer.Default); - }) as IMethodSymbol; + .OfType() + .WhereAsArray(m => m.IsOverride && overriddenMethod.Equals(m.GetOverriddenMember(), SymbolEqualityComparer.Default)); } // We will not be doing any comparisons on this type. diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs index 84410711ee..2343446f53 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs @@ -1093,7 +1093,253 @@ End Class } #endregion + #region Does Not Crash On Illegal Code + [Theory] + [InlineData(ReadAsyncName, CSReadAsyncArray)] + [InlineData(WriteAsyncName, CSWriteAsyncArray)] + public Task DuplicateArrayOverrides_WithoutMemoryOverride_ReportsDiagnosticWithoutCrashing_CS(string methodName, string methodDefinition) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class {{|#0:River|}} : Stream + {{ + {CSAbstractMembers} + {methodDefinition} + {methodDefinition.Replace(methodName, $"{{|#1:{methodName}|}}")} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + new DiagnosticResult(RuleId, DiagnosticSeverity.Info) + .WithLocation(0), + new DiagnosticResult(CSMemberHasMultipleDefinitionsRuleId, DiagnosticSeverity.Error) + .WithLocation(1) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(ReadAsyncName, VBReadAsyncArray)] + [InlineData(WriteAsyncName, VBWriteAsyncArray)] + public Task DuplicateArrayOverrides_WithoutMemoryOverride_ReportsDiagnosticWithoutCrashing_VB(string methodName, string methodDefinition) + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class {{|#0:River|}} : Inherits Stream + {VBAbstractMembers} + {SurroundWithMarkup(methodDefinition, methodName, 1)} + {methodDefinition} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + new DiagnosticResult(RuleId, DiagnosticSeverity.Info) + .WithLocation(0), + new DiagnosticResult(VBMemberHasMultipleDefinitionsRuleId, DiagnosticSeverity.Error) + .WithLocation(1) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(ReadAsyncName, CSReadAsyncArray, CSReadAsyncMemory)] + [InlineData(WriteAsyncName, CSWriteAsyncArray, CSWriteAsyncMemory)] + public Task DuplicateArrayOverrides_WithMemoryOverride_NoDiagnostic_NoCrash_CS(string methodName, string arrayMethodDefinition, string memoryMethodDefinition) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class River : Stream + {{ + {CSAbstractMembers} + {arrayMethodDefinition} + {SurroundWithMarkup(arrayMethodDefinition, methodName, 0)} + {memoryMethodDefinition} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + new DiagnosticResult(CSMemberHasMultipleDefinitionsRuleId, DiagnosticSeverity.Error) + .WithLocation(0) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(ReadAsyncName, VBReadAsyncArray, VBReadAsyncMemory)] + [InlineData(WriteAsyncName, VBWriteAsyncArray, VBWriteAsyncMemory)] + public Task DuplicateArrayOverrides_WithMemoryOverride_NoDiagnostic_NoCrash_VB(string methodName, string arrayMethodDefinition, string memoryMethodDefinition) + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class River : Inherits Stream + {VBAbstractMembers} + {SurroundWithMarkup(arrayMethodDefinition, methodName, 0)} + {arrayMethodDefinition} + {memoryMethodDefinition} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + new DiagnosticResult(VBMemberHasMultipleDefinitionsRuleId, DiagnosticSeverity.Error) + .WithLocation(0) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(ReadAsyncName, CSReadAsyncArray, CSReadAsyncMemory)] + [InlineData(WriteAsyncName, CSWriteAsyncArray, CSWriteAsyncMemory)] + public Task DuplicateMemoryOverrides_WithArrayOverride_NoDiagnostic_NoCrash_CS(string methodName, string arrayMethodDefinition, string memoryMethodDefinition) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class River : Stream + {{ + {CSAbstractMembers} + {arrayMethodDefinition} + {memoryMethodDefinition} + {SurroundWithMarkup(memoryMethodDefinition, methodName, 0)} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + new DiagnosticResult(CSMemberHasMultipleDefinitionsRuleId, DiagnosticSeverity.Error) + .WithLocation(0) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(ReadAsyncName, VBReadAsyncArray, VBReadAsyncMemory)] + [InlineData(WriteAsyncName, VBWriteAsyncArray, VBWriteAsyncMemory)] + public Task DuplicateMemoryOverrides_WithArrayOverride_NoDiagnostic_NoCrash_VB(string methodName, string arrayMethodDefinition, string memoryMethodDefinition) + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class River : Inherits Stream + {VBAbstractMembers} + {arrayMethodDefinition} + {SurroundWithMarkup(memoryMethodDefinition, methodName, 0)} + {memoryMethodDefinition} + + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + new DiagnosticResult(VBMemberHasMultipleDefinitionsRuleId, DiagnosticSeverity.Error) + .WithLocation(0) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(ReadAsyncName, CSReadAsyncMemory)] + [InlineData(WriteAsyncName, CSWriteAsyncMemory)] + public Task DuplicateMemoryOverrides_NoArrayOverride_NoDiagnostic_NoCrash_CS(string methodName, string memoryMethodDefinition) + { + string code = $@" +{CSUsings} +namespace Testopolis +{{ + public class River : Stream + {{ + {CSAbstractMembers} + {memoryMethodDefinition} + {SurroundWithMarkup(memoryMethodDefinition, methodName, 0)} + }} +}}"; + + var test = new VerifyCS.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + new DiagnosticResult(CSMemberHasMultipleDefinitionsRuleId, DiagnosticSeverity.Error) + .WithLocation(0) + } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(ReadAsyncName, VBReadAsyncMemory)] + [InlineData(WriteAsyncName, VBWriteAsyncMemory)] + public Task DuplicateMemoryOverrides_NoArrayOverride_NoDiagnostic_NoCrash_VB(string methodName, string memoryMethodDefinition) + { + string code = $@" +{VBUsings} +Namespace Testopolis + Public Class River : Inherits Stream + {VBAbstractMembers} + {SurroundWithMarkup(memoryMethodDefinition, methodName, 0)} + {memoryMethodDefinition} + End Class +End Namespace"; + + var test = new VerifyVB.Test + { + TestCode = code, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + new DiagnosticResult(VBMemberHasMultipleDefinitionsRuleId, DiagnosticSeverity.Error) + .WithLocation(0) + } + }; + return test.RunAsync(); + } + #endregion + #region Helpers + private const string ReadAsyncName = nameof(System.IO.Stream.ReadAsync); + private const string WriteAsyncName = nameof(System.IO.Stream.WriteAsync); + private const string CSUsings = @"using System; using System.IO; using System.Threading; @@ -1216,7 +1462,15 @@ Throw New NotImplementedException() private static DiagnosticDescriptor Rule => ProvideStreamMemoryBasedAsyncOverrides.Rule; private static string RuleId => ProvideStreamMemoryBasedAsyncOverrides.RuleId; private const string CSMemberHidesBaseRuleId = "CS0114"; + private const string CSMemberHasMultipleDefinitionsRuleId = "CS0111"; + private const string VBMemberHidesBaseRuleId = "BC40005"; + private const string VBMemberHasMultipleDefinitionsRuleId = "BC30269"; + + private static string SurroundWithMarkup(string source, string substring, int markupKey) + { + return source.Replace(substring, $"{{|#{markupKey}:{substring}|}}"); + } #endregion } } From 3f64c5e7f2e3d6773e0f96b0251979977b136d83 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Fri, 22 Jan 2021 15:17:22 -0500 Subject: [PATCH 017/224] Rerun CI --- .../Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs index b207209358..7162422dc9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs @@ -183,7 +183,7 @@ private static ImmutableArray GetOverridingMethodSymbols(ITypeSym .WhereAsArray(m => m.IsOverride && overriddenMethod.Equals(m.GetOverriddenMember(), SymbolEqualityComparer.Default)); } - // We will not be doing any comparisons on this type. + // We will not be doing any comparisons on this type #pragma warning disable CA1815 // Override equals and operator equals on value types private readonly struct RequiredSymbols #pragma warning restore CA1815 // Override equals and operator equals on value types From f91a7f0acc8a5bf5b2b00932db0b4d64545113f7 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sat, 23 Jan 2021 12:47:14 -0500 Subject: [PATCH 018/224] Apply suggested changes --- .../MicrosoftNetCoreAnalyzersResources.resx | 2 +- .../ProvideStreamMemoryBasedAsyncOverrides.cs | 21 ++++++++++--------- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 4 ++-- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 4 ++-- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 4 ++-- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 4 ++-- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 4 ++-- 15 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 52984e8891..39e6c0cecb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1511,7 +1511,7 @@ This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. '{0}' overrides '{1}' but does not override '{2}'. Consider overriding '{2}' to improve performance. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs index 7162422dc9..97d279cfbb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Immutable; using System.Composition; @@ -98,10 +98,8 @@ static Diagnostic CreateDiagnostic(INamedTypeSymbol violatingType, IMethodSymbol // We want to underline the name of the violating type in the class declaration. If the violating type // is a partial class, we underline all partial declarations. - return Diagnostic.Create( + return violatingType.CreateDiagnostic( Rule, - violatingType.Locations[0], - violatingType.Locations.Skip(1), violatingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), arrayBasedOverride.OverriddenMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), memoryBasedMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); @@ -124,12 +122,13 @@ private static bool TryGetRequiredSymbols(Compilation compilation, out RequiredS return false; } - // We know that there are no compiler errors on streamType, so we use 'SingleOrDefault'. + // Even though framework types should never contain compiler errors, we still use 'FirstOrDefault' to avoid having the + // analyzer crash if it runs against the source code for 'System.Private.CoreLib', which could be malformed. - var readAsyncArrayMethod = GetOverloads(streamType, ReadAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType).SingleOrDefault(); - var readAsyncMemoryMethod = GetOverloads(streamType, ReadAsyncName, memoryOfByteType, cancellationTokenType).SingleOrDefault(); - var writeAsyncArrayMethod = GetOverloads(streamType, WriteAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType).SingleOrDefault(); - var writeAsyncMemoryMethod = GetOverloads(streamType, WriteAsyncName, readOnlyMemoryOfByteType, cancellationTokenType).SingleOrDefault(); + var readAsyncArrayMethod = GetOverloads(streamType, ReadAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType).FirstOrDefault(); + var readAsyncMemoryMethod = GetOverloads(streamType, ReadAsyncName, memoryOfByteType, cancellationTokenType).FirstOrDefault(); + var writeAsyncArrayMethod = GetOverloads(streamType, WriteAsyncName, byteArrayType, int32Type, int32Type, cancellationTokenType).FirstOrDefault(); + var writeAsyncMemoryMethod = GetOverloads(streamType, WriteAsyncName, readOnlyMemoryOfByteType, cancellationTokenType).FirstOrDefault(); if (readAsyncArrayMethod is null || readAsyncMemoryMethod is null || writeAsyncArrayMethod is null || writeAsyncMemoryMethod is null) { @@ -183,7 +182,9 @@ private static ImmutableArray GetOverridingMethodSymbols(ITypeSym .WhereAsArray(m => m.IsOverride && overriddenMethod.Equals(m.GetOverriddenMember(), SymbolEqualityComparer.Default)); } - // We will not be doing any comparisons on this type + // We use a struct instead of a record-type to save on allocations. + // There is no trade-off for doing so because this type is never passed by-value. + // We never do equality operations on instances. #pragma warning disable CA1815 // Override equals and operator equals on value types private readonly struct RequiredSymbols #pragma warning restore CA1815 // Override equals and operator equals on value types diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 4ea13a7af1..c545921c2d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index e7ca597a28..bef77f22aa 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 53447ec222..71bb1e8c27 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index b9ab6c37e6..7f088e02f1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index ca8748f242..cb5a9b57ca 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 7e973d459d..9cd44c3ac7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index f2c2e5cbc2..00467adae6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 257cf4073e..5586194640 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index d10ad4d4da..d9b10d58fa 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index a66f132034..4860a2e55e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index a97dab61e3..e746102deb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 340cc04f65..14d4b8ca14 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index dbfebf63a5..3a6c9e460b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -1723,8 +1723,8 @@ - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. - The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. + The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. From f072328b4d51a4511b879d0b7b4c1e4172db9cd5 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sat, 23 Jan 2021 13:47:53 -0500 Subject: [PATCH 019/224] Rerun CI --- .../Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs index 97d279cfbb..3a3a9ba37e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryBasedAsyncOverrides.cs @@ -171,7 +171,7 @@ static bool IsMatch(IMethodSymbol method, ITypeSymbol[] argumentTypes) /// /// The type that may have overridden a method on its immediate base-class. /// - /// The for the method that overrides , or null if + /// The for the method that overrides , or null if /// is not overridden. private static ImmutableArray GetOverridingMethodSymbols(ITypeSymbol derivedType, IMethodSymbol overriddenMethod) { From faac3a885cfff1b9d10e09c7f5d833c99b321679 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sat, 23 Jan 2021 14:20:10 -0500 Subject: [PATCH 020/224] Forgot to msbuild /t:pack --- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md | 2 +- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 0cd7f9fa21..0485c9f674 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1298,7 +1298,7 @@ Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in ## [CA1840](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840): Provide memory-based overrides of async methods when subclassing 'Stream' -The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'. +The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'. |Item|Value| |-|-| diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 0a4c80777d..94eafc1d15 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -2445,7 +2445,7 @@ "CA1840": { "id": "CA1840", "shortDescription": "Provide memory-based overrides of async methods when subclassing 'Stream'", - "fullDescription": "The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving your implementation to '{2}' and have '{1}' delegate to '{2}'.", + "fullDescription": "The default implementation of '{2}' delegates to '{1}', which is innefficient as it requires an extra copy. It also results in extra allocations as it returns Task instead of ValueTask. Consider moving {0}'s implementation to '{2}' and have '{1}' delegate to '{2}'.", "defaultLevel": "note", "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840", "properties": { From 19409952ff240f9f43f7687f92b67b5fca2b4d67 Mon Sep 17 00:00:00 2001 From: Newell Clark Date: Sun, 24 Jan 2021 11:10:56 -0500 Subject: [PATCH 021/224] Fix typo Co-authored-by: Youssef Victor <31348972+Youssef1313@users.noreply.github.com> --- .../Runtime/ProvideStreamMemoryAsyncOverridesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs index 2343446f53..4b5d0b87f0 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ProvideStreamMemoryAsyncOverridesTests.cs @@ -396,7 +396,7 @@ partial class {{|#2:River|}} } [Fact] - public Task BothArrayOverrides_MultiplePartialsInSameFile_REportsAllLocations_VB() + public Task BothArrayOverrides_MultiplePartialsInSameFile_ReportsAllLocations_VB() { string code = $@" {VBUsings} From ba6d78d95a254078fbca3da1aacd4d0e289f2eeb Mon Sep 17 00:00:00 2001 From: NewellClark Date: Mon, 25 Jan 2021 13:24:02 -0500 Subject: [PATCH 022/224] Add required files --- .../Core/AnalyzerReleases.Unshipped.md | 5 +++ .../MicrosoftNetCoreAnalyzersResources.resx | 9 ++++ .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 21 +++++++++ .../Runtime/UseSpanBasedStringConcat.cs | 43 +++++++++++++++++++ .../Runtime/UseSpanBasedStringConcatTests.cs | 20 +++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 64372afc0b..bf76e5ec66 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1,5 +1,10 @@ ; Please do not edit this file manually, it should only be updated through code fix application. +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +CA1841 | Performance | Info | UseSpanBasedStringConcat, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841) + ### Removed Rules Rule ID | Category | Severity | Notes diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 0b8e62a9ba..af0257a751 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1510,4 +1510,13 @@ and all other platforms This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms + + description + + + message + + + title + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs new file mode 100644 index 0000000000..4580f730cf --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class UseSpanBasedStringConcatFixer : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs new file mode 100644 index 0000000000..92a8f76024 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using Analyzer.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class UseSpanBasedStringConcat : DiagnosticAnalyzer + { + internal const string RuleId = "CA1841"; + + private static readonly LocalizableString s_localizableTitle = CreateResource(nameof(Resx.UseSpanBasedStringConcatTitle)); + private static readonly LocalizableString s_localizableMessage = CreateResource(nameof(Resx.UseSpanBasedStringConcatMessage)); + private static readonly LocalizableString s_localizableDescription = CreateResource(nameof(Resx.UseSpanBasedStringConcatDescription)); + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + s_localizableTitle, + s_localizableMessage, + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + throw new NotImplementedException(); + } + + private static LocalizableString CreateResource(string resourceName) + { + return new LocalizableResourceString(resourceName, Resx.ResourceManager, typeof(Resx)); + } + } +} diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs new file mode 100644 index 0000000000..8f68eb73f0 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat, + Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcatFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat, + Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcatFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class UseSpanBasedStringConcatTests + { + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 94d4940230..6847720374 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1310 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1838 +Performance: HA, CA1800-CA1841 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5403 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2249 Naming: CA1700-CA1726 From cc5cf15290f47120a2be9fd5d8140eeb49eab46c Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Thu, 28 Jan 2021 14:09:35 -0800 Subject: [PATCH 023/224] Fixup feeds --- NuGet.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NuGet.config b/NuGet.config index 2f3a4009c4..4dc3fb75d0 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,9 +6,9 @@ - + - + \ No newline at end of file From 42107f2320ea1a566a9b319d45819dba4da40696 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sun, 31 Jan 2021 11:01:45 -0500 Subject: [PATCH 024/224] Common cases --- .../CSharpUseSpanBasedStringConcat.Fixer.cs | 55 ++ .../Runtime/CSharpUseSpanBasedStringConcat.cs | 19 + .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 165 +++- .../Runtime/UseSpanBasedStringConcat.cs | 235 +++++- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 15 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 15 + .../Runtime/UseSpanBasedStringConcatTests.cs | 719 +++++++++++++++++- .../BasicUseSpanBasedStringConcat.Fixer.vb | 65 ++ .../Runtime/BasicUseSpanBasedStringConcat.vb | 18 + src/Utilities/Compiler/WellKnownTypeNames.cs | 1 + 21 files changed, 1457 insertions(+), 15 deletions(-) create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs new file mode 100644 index 0000000000..23e54c8576 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.NetCore.Analyzers.Runtime; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + public sealed class CSharpUseSpanBasedStringConcatFixer : UseSpanBasedStringConcatFixer + { + private protected override SyntaxNode ReplaceInvocationMethodName(SyntaxGenerator generator, SyntaxNode invocationSyntax, string newName) + { + var memberAccessSyntax = (MemberAccessExpressionSyntax)((InvocationExpressionSyntax)invocationSyntax).Expression; + var oldNameSyntax = memberAccessSyntax.Name; + var newNameSyntax = generator.IdentifierName(newName).WithTriviaFrom(oldNameSyntax); + return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax); + } + + private protected override SyntaxToken GetOperatorToken(IBinaryOperation binaryOperation) + { + var syntax = (BinaryExpressionSyntax)binaryOperation.Syntax; + return syntax.OperatorToken; + } + + private protected override bool IsSystemNamespaceImported(IReadOnlyList namespaceImports) + { + foreach (var import in namespaceImports) + { + if (import is UsingDirectiveSyntax { Name: IdentifierNameSyntax { Identifier: { ValueText: nameof(System) } } }) + return true; + } + return false; + } + + private protected override bool IsNamedArgument(IArgumentOperation argument) + { + var node = (ArgumentSyntax)argument.Syntax; + return node.NameColon is not null; + } + + private protected override SyntaxNode CreateConditionalToStringInvocation(SyntaxNode receiverExpression) + { + var expression = (ExpressionSyntax)receiverExpression; + var memberBindingExpression = SyntaxFactory.MemberBindingExpression(SyntaxFactory.IdentifierName(ToStringName)); + var toStringInvocationExpression = SyntaxFactory.InvocationExpression(memberBindingExpression); + return SyntaxFactory.ConditionalAccessExpression(expression, toStringInvocationExpression); + } + } +} diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs new file mode 100644 index 0000000000..e62e776911 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.NetCore.Analyzers.Runtime; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class CSharpUseSpanBasedStringConcat : UseSpanBasedStringConcat + { + private protected override bool IsStringConcatOperation(IBinaryOperation binaryOperation) + { + return binaryOperation.OperatorKind == BinaryOperatorKind.Add && + binaryOperation.Type.SpecialType == SpecialType.System_String; + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 4580f730cf..0b1c09cbdc 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -1,21 +1,176 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; +using System.Threading; using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; + +using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; +using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat.RequiredSymbols; namespace Microsoft.NetCore.Analyzers.Runtime { - [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public sealed class UseSpanBasedStringConcatFixer : CodeFixProvider + public abstract class UseSpanBasedStringConcatFixer : CodeFixProvider { - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); + private const string AsSpanName = nameof(MemoryExtensions.AsSpan); + private const string AsSpanStartParameterName = "start"; + private protected const string ToStringName = nameof(ToString); + + private protected abstract SyntaxNode ReplaceInvocationMethodName(SyntaxGenerator generator, SyntaxNode invocationSyntax, string newName); + + private protected abstract SyntaxToken GetOperatorToken(IBinaryOperation binaryOperation); + + private protected abstract bool IsSystemNamespaceImported(IReadOnlyList namespaceImports); + + private protected abstract bool IsNamedArgument(IArgumentOperation argument); + + /// + /// Invokes on the specified expression using the Elvis operator. + /// + private protected abstract SyntaxNode CreateConditionalToStringInvocation(SyntaxNode receiverExpression); + + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); - public override Task RegisterCodeFixesAsync(CodeFixContext context) + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { - throw new NotImplementedException(); + var document = context.Document; + var diagnostic = context.Diagnostics.First(); + var cancellationToken = context.CancellationToken; + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var compilation = model.Compilation; + SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + // Bail out early if we're missing anything we need. + if (!RequiredSymbols.TryGetSymbols(compilation, out var symbols)) + return; + if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode concatExpressionSyntax) + return; + if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind != symbols.ConcatOperatorKind) + return; + + var operands = UseSpanBasedStringConcat.FlattenBinaryOperationChain(concatOperation); + // Bail out if we don't have a long enough span-based string.Concat overload. + if (!symbols.TryGetRoscharConcatMethodWithArity(operands.Length, out IMethodSymbol? roscharConcatMethod)) + return; + + var codeAction = CodeAction.Create( + Resx.UseSpanBasedStringConcatTitle, + FixConcatOperationChain, + Resx.UseSpanBasedStringConcatTitle); + context.RegisterCodeFix(codeAction, diagnostic); + + async Task FixConcatOperationChain(CancellationToken cancellationToken) + { + RoslynDebug.Assert(roscharConcatMethod is not null); + + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var generator = editor.Generator; + + // Save leading and trailing trivia so it can be attached to the outside of the 'string.Concat(...)' invocation expression. + var leadingTrivia = operands.First().Syntax.GetLeadingTrivia(); + var trailingTrivia = operands.Last().Syntax.GetTrailingTrivia(); + + SyntaxNode stringTypeNameSyntax = generator.TypeExpressionForStaticMemberAccess(symbols.StringType); + SyntaxNode concatMemberAccessSyntax = generator.MemberAccessExpression(stringTypeNameSyntax, roscharConcatMethod.Name); + var arguments = GenerateConcatMethodArguments(symbols, generator, operands); + SyntaxNode concatMethodInvocationSyntax = generator.InvocationExpression(concatMemberAccessSyntax, arguments) + .WithLeadingTrivia(leadingTrivia) + .WithTrailingTrivia(trailingTrivia); + var newRoot = generator.ReplaceNode(root, concatExpressionSyntax, concatMethodInvocationSyntax); + + // Make sure 'System' namespace is imported. + if (!IsSystemNamespaceImported(generator.GetNamespaceImports(newRoot))) + { + var systemNamespaceImport = generator.NamespaceImportDeclaration(nameof(System)); + newRoot = generator.AddNamespaceImports(newRoot, systemNamespaceImport); + } + + editor.ReplaceNode(root, newRoot); + return editor.GetChangedDocument(); + } + } + + private ImmutableArray GenerateConcatMethodArguments(in RequiredSymbols symbols, SyntaxGenerator generator, ImmutableArray operands) + { + var builder = ImmutableArray.CreateBuilder(operands.Length); + foreach (IOperation operand in operands) + { + // Convert 'Substring' invocations into 'AsSpan' invocations. + if (WalkDownImplicitConversion(operand) is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) + { + SyntaxNode newInvocationSyntax = invocation.Syntax; + + // Convert 'Substring' named-arguments to equivalent 'AsSpan' named arguments. + IArgumentOperation? namedStartIndexArgument = GetNamedStartIndexArgumentOrDefault(symbols, invocation); + if (namedStartIndexArgument is not null) + { + SyntaxNode renamedSubstringArgumentSyntax = generator.Argument(AsSpanStartParameterName, RefKind.None, namedStartIndexArgument.Value.Syntax); + newInvocationSyntax = generator.ReplaceNode(newInvocationSyntax, namedStartIndexArgument.Syntax, renamedSubstringArgumentSyntax); + } + + // Replace 'Substring' identifier with 'AsSpan', leaving the rest of the node (including trivia) intact. + newInvocationSyntax = ReplaceInvocationMethodName(generator, newInvocationSyntax, AsSpanName); + builder.Add(generator.Argument(newInvocationSyntax)); + } + else + { + IOperation value = WalkDownImplicitConversion(operand); + if (value.Type.SpecialType == SpecialType.System_String) + { + builder.Add(generator.Argument(value.Syntax)); + } + else + { + SyntaxNode newValueSyntax; + if (value.Type.IsReferenceTypeOrNullableValueType()) + { + newValueSyntax = CreateConditionalToStringInvocation(value.Syntax); + } + else + { + SyntaxNode toStringMemberAccessSyntax = generator.MemberAccessExpression(value.Syntax.WithoutTrivia(), ToStringName); + newValueSyntax = generator.InvocationExpression(toStringMemberAccessSyntax, Array.Empty()) + .WithTriviaFrom(value.Syntax); + } + builder.Add(generator.Argument(newValueSyntax)); + } + } + } + + builder[0] = builder[0].WithoutLeadingTrivia(); + builder[^1] = builder[^1].WithoutTrailingTrivia(); + return builder.MoveToImmutable(); + + // If the 'startIndex' argument was passed using named-arguments, return it. + // Otherwise, return null. + IArgumentOperation? GetNamedStartIndexArgumentOrDefault(in RequiredSymbols symbols, IInvocationOperation substringInvocation) + { + RoslynDebug.Assert(symbols.IsAnySubstringMethod(substringInvocation.TargetMethod)); + foreach (var argument in substringInvocation.Arguments) + { + if (IsNamedArgument(argument) && symbols.IsAnySubstringStartIndexParameter(argument.Parameter)) + return argument; + } + return null; + } + + static IOperation WalkDownImplicitConversion(IOperation operand) + { + if (operand is IConversionOperation { Type: { SpecialType: SpecialType.System_Object or SpecialType.System_String }, IsImplicit: true } conversion) + return conversion.Operand; + return operand; + } } + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index 92a8f76024..6fe7fc6fd0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -3,14 +3,19 @@ using System; using System.Collections.Immutable; using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; +using Analyzer.Utilities.PooledObjects; +using System.Collections.Generic; +using System.Linq; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.NetCore.Analyzers.Runtime { - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public sealed class UseSpanBasedStringConcat : DiagnosticAnalyzer + public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer { internal const string RuleId = "CA1841"; @@ -28,11 +33,231 @@ public sealed class UseSpanBasedStringConcat : DiagnosticAnalyzer isPortedFxCopRule: false, isDataflowRule: false); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + private const string SubstringName = nameof(string.Substring); + private const string AsSpanName = nameof(MemoryExtensions.AsSpan); + private const string ConcatName = nameof(string.Concat); - public override void Initialize(AnalysisContext context) + private protected abstract bool IsStringConcatOperation(IBinaryOperation binaryOperation); + + public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public sealed override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private void OnCompilationStart(CompilationStartAnalysisContext context) + { + if (!RequiredSymbols.TryGetSymbols(context.Compilation, out RequiredSymbols symbols)) + return; + + context.RegisterOperationBlockStartAction(OnOperationBlockStart); + + void OnOperationBlockStart(OperationBlockStartAnalysisContext context) + { + var rootConcatOperations = PooledConcurrentSet.GetInstance(); + + context.RegisterOperationAction(PopulateRootConcatOperations, OperationKind.Binary); + context.RegisterOperationBlockEndAction(ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls); + + void PopulateRootConcatOperations(OperationAnalysisContext context) + { + var binary = (IBinaryOperation)context.Operation; + if (binary.OperatorKind != symbols.ConcatOperatorKind) + return; + + var topBinaryOperation = WalkUpBinaryOperationChain(binary); + if (!IsStringConcatOperation(topBinaryOperation)) + return; + + rootConcatOperations.Add(topBinaryOperation); + } + + void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context) + { + foreach (var root in rootConcatOperations) + { + var chain = FlattenBinaryOperationChain(root); + if (chain.Any(IsAnySubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out var _)) + { + context.ReportDiagnostic(root.CreateDiagnostic(Rule)); + } + } + rootConcatOperations.Free(context.CancellationToken); + } + } + + bool IsAnySubstringInvocation(IOperation operation) + { + return operation.WalkDownConversion() is IInvocationOperation invocation && + (invocation.TargetMethod.Equals(symbols.Substring1, SymbolEqualityComparer.Default) || + invocation.TargetMethod.Equals(symbols.Substring2, SymbolEqualityComparer.Default)); + } + } + + private static IBinaryOperation WalkUpBinaryOperationChain(IBinaryOperation operation) { - throw new NotImplementedException(); + while (operation.Parent is IBinaryOperation parentBinaryOperation && + parentBinaryOperation.OperatorKind == operation.OperatorKind) + { + operation = parentBinaryOperation; + } + + return operation; + } + + internal static ImmutableArray FlattenBinaryOperationChain(IBinaryOperation root) + { + var stack = new Stack(); + var builder = ImmutableArray.CreateBuilder(); + GoLeft(root); + + while (stack.Count != 0) + { + var current = stack.Pop(); + + if (current.LeftOperand is not IBinaryOperation leftBinary || leftBinary.OperatorKind != root.OperatorKind) + { + builder.Add(current.LeftOperand); + } + + if (current.RightOperand is not IBinaryOperation rightBinary || rightBinary.OperatorKind != root.OperatorKind) + { + builder.Add(current.RightOperand); + } + else + { + GoLeft(rightBinary); + } + } + + return builder.ToImmutable(); + + void GoLeft(IBinaryOperation operation) + { + IBinaryOperation? current = operation; + while (current is not null && current.OperatorKind == root.OperatorKind) + { + stack.Push(current); + current = current.LeftOperand as IBinaryOperation; + } + } + } + + // Use readonly struct instead of record type to save on allocations, since it's not passed by-value. + // We aren't comparing these. +#pragma warning disable CA1815 // Override equals and operator equals on value types + internal readonly struct RequiredSymbols +#pragma warning restore CA1815 // Override equals and operator equals on value types + { + private RequiredSymbols( + INamedTypeSymbol stringType, INamedTypeSymbol roscharType, + IMethodSymbol substring1, IMethodSymbol substring2, + IMethodSymbol asSpan1, IMethodSymbol asSpan2, + BinaryOperatorKind concatOperatorKind) + { + StringType = stringType; + RoscharType = roscharType; + Substring1 = substring1; + Substring2 = substring2; + AsSpan1 = asSpan1; + AsSpan2 = asSpan2; + ConcatOperatorKind = concatOperatorKind; + + RoslynDebug.Assert( + StringType is not null && RoscharType is not null && + Substring1 is not null && Substring2 is not null && + AsSpan1 is not null && AsSpan2 is not null); + } + + public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) + { + var stringType = compilation.GetSpecialType(SpecialType.System_String); + var roscharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1) + ?.Construct(compilation.GetSpecialType(SpecialType.System_Char)); + var memoryExtensionsType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemoryExtensions); + + if (roscharType is null || memoryExtensionsType is null) + { + symbols = default; + return false; + } + + var intParamInfo = ParameterInfo.GetParameterInfo(compilation.GetSpecialType(SpecialType.System_Int32)); + var stringParamInfo = ParameterInfo.GetParameterInfo(stringType); + + var substringMembers = stringType.GetMembers(SubstringName).OfType(); + var substring1 = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo); + var substring2 = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo, intParamInfo); + + var asSpanMembers = memoryExtensionsType.GetMembers(AsSpanName).OfType(); + var asSpan1 = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); + var asSpan2 = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); + + if (substring1 is null || substring2 is null || asSpan1 is null || asSpan2 is null) + { + symbols = default; + return false; + } + + var concatOperatorKind = compilation.Language switch + { + LanguageNames.CSharp => BinaryOperatorKind.Add, + LanguageNames.VisualBasic => BinaryOperatorKind.Concatenate, + _ => BinaryOperatorKind.None + }; + + symbols = new RequiredSymbols( + stringType, roscharType, + substring1, substring2, + asSpan1, asSpan2, + concatOperatorKind); + return true; + } + + public INamedTypeSymbol StringType { get; } + public INamedTypeSymbol RoscharType { get; } + public IMethodSymbol Substring1 { get; } + public IMethodSymbol Substring2 { get; } + public IMethodSymbol AsSpan1 { get; } + public IMethodSymbol AsSpan2 { get; } + public BinaryOperatorKind ConcatOperatorKind { get; } + + public IMethodSymbol? GetAsSpanEquivalent(IMethodSymbol? substringMethod) + { + if (SymbolEqualityComparer.Default.Equals(substringMethod, Substring1)) + return AsSpan1; + if (SymbolEqualityComparer.Default.Equals(substringMethod, Substring2)) + return AsSpan2; + return null; + } + + public bool IsAnySubstringMethod(IMethodSymbol? method) + { + return SymbolEqualityComparer.Default.Equals(method, Substring1) || + SymbolEqualityComparer.Default.Equals(method, Substring2); + } + + public bool IsAnySubstringStartIndexParameter(IParameterSymbol? parameter) + { + return SymbolEqualityComparer.Default.Equals(parameter, Substring1.Parameters.First()) || + SymbolEqualityComparer.Default.Equals(parameter, Substring2.Parameters.First()); + } + + public bool TryGetRoscharConcatMethodWithArity(int arity, [NotNullWhen(true)] out IMethodSymbol? concatMethod) + { + var roscharParamInfo = ParameterInfo.GetParameterInfo(RoscharType); + var argumentList = new ParameterInfo[arity]; + for (int index = 0; index < arity; index++) + argumentList[index] = roscharParamInfo; + + concatMethod = StringType.GetMembers(ConcatName) + .OfType() + .GetFirstOrDefaultMemberWithParameterInfos(argumentList); + return concatMethod is not null; + } } private static LocalizableString CreateResource(string resourceName) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 714e9e49b4..d6dbb80219 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2237,6 +2237,21 @@ Pokud je to možné, zvažte použití řízení přístupu Azure na základě role namísto sdíleného přístupového podpisu (SAS). Pokud i přesto potřebujete používat sdílený přístupový podpis, zadejte SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Hodnoty ValueTask vrácené z vyvolání členů jsou určené k tomu, aby byly přímo očekávané. Pokusy o vícenásobné využití ValueTask nebo o přímý přístup k výsledku úkolu před tím, než je známo, že je dokončený, můžou způsobit výjimku nebo poškození. Ignorování takové hodnoty ValueTask je pravděpodobně indikací funkční chyby a může snížit výkon. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 6e0f153a85..74a68f8bab 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2237,6 +2237,21 @@ Erwägen Sie (sofern möglich) die Verwendung der rollenbasierten Zugriffssteuerung von Azure anstelle einer Shared Access Signature (SAS). Wenn Sie weiterhin eine SAS benötigen, verwenden Sie "SharedAccessProtocol.HttpsOnly". + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Von Memberaufrufen zurückgegebene ValueTasks sind so konzipiert, dass sie direkt erwartet werden sollten. Versuche, einen ValueTask mehrmals zu nutzen oder direkt auf das Ergebnis zuzugreifen, bevor er offiziell abgeschlossen wurde, können zu einer Ausnahme oder zur Beschädigung führen. Das Ignorieren eines solchen ValueTask ist wahrscheinlich ein Hinweis auf einen Funktionsfehler und kann zu Leistungseinbußen führen. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 8c66938819..e2d072c08e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2237,6 +2237,21 @@ Considere la posibilidad de usar el control de acceso basado en rol de Azure en lugar de una firma de acceso compartido (SAS), si es posible. Si tiene que usar una firma de acceso compartido, especifique SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Los elementos ValueTask devueltos por invocaciones de miembros están diseñados para esperarlos directamente. Los intentos de consumir un ValueTask varias veces o de acceder directamente al resultado de uno antes de que se sepa que se ha completado pueden producir una excepción o daños en los datos. La omisión de un ValueTask en este caso indica probablemente un error funcional y puede degradar el rendimiento. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 71a0c15261..20c591156f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2237,6 +2237,21 @@ Si possible, utilisez le contrôle d'accès en fonction du rôle d'Azure à la place d'une signature d'accès partagé. Si vous devez quand même utiliser une signature d'accès partagé, spécifiez SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Les ValueTasks retournés par les appels de membres sont censés être directement attendus. Les tentatives de consommation d'un ValueTask à plusieurs reprises ou d'accès direct à son résultat avant la fin de l'opération peuvent entraîner une exception ou une altération des données. Quand un ValueTask est ainsi ignoré, cela indique probablement un bogue fonctionnel et un risque de dégradation du niveau de performance. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 61adbbacb6..477cf058b9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2237,6 +2237,21 @@ Se possibile, provare a usare il controllo degli accessi in base al ruolo di Azure, invece della firma di accesso condiviso. Se è necessario usare una firma di accesso condiviso, specificare SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Gli elementi ValueTask restituiti da chiamate ai membri devono essere usati direttamente tramite await. Se si prova a utilizzare più volte un elemento ValueTask oppure ad accedere direttamente al risultato di un oggetto prima che sia realmente completato, potrebbe verificarsi eccezioni o danneggiamenti dei dati. Un elemento ValueTask ignorato è probabilmente indicativo di un bug funzionale e potrebbe influire negativamente sulle prestazioni. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 4faf2ebe51..4b4dbc362d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2237,6 +2237,21 @@ 可能な場合は、Shared Access Signature (SAS) の代わりに、Azure のロールベースのアクセス制御を使用することを検討してください。依然として SAS を使用する必要がある場合は、SharedAccessProtocol.HttpsOnly を指定します。 + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. メンバー呼び出しから返される ValueTask は、直接待機されるよう意図されています。1 つの ValueTask を複数回使用しようとしたり、完了が判明する前に結果に直接アクセスしようとしたりすると、例外または破損が発生する可能性があります。このような ValueTask を無視することは、機能的なバグを示していることが多く、パフォーマンスを低下させる可能性があります。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 42e160e005..8c22daf246 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2237,6 +2237,21 @@ 가능한 경우 SAS(공유 액세스 서명) 대신 Azure의 역할 기반 액세스 제어를 사용하는 것이 좋습니다. 계속 SAS를 사용해야 하는 경우 SharedAccessProtocol.HttpsOnly를 지정하세요. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. 멤버 호출에서 반환되는 ValueTasks는 바로 대기되기 위한 것입니다. ValueTask를 여러 번 사용하거나, 완료가 확인되기 전에 해당 결과에 바로 액세스하면 예외나 손상이 발생할 수 있습니다. 이러한 ValueTask를 무시하면 기능 버그가 발생하거나 성능이 저하될 수 있습니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index dd9ad6db14..4b9053b0a0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2237,6 +2237,21 @@ Jeśli to możliwe, rozważ użycie kontroli dostępu opartej na rolach platformy Azure zamiast sygnatury dostępu współdzielonego (SAS). Jeśli nadal chcesz używać sygnatury SAS, określ właściwość SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Elementy ValueTask zwracane przez wywołania elementów członkowskich są przeznaczone do bezpośredniego oczekiwania. Próby wielokrotnego użycia elementu ValueTask lub uzyskania bezpośredniego dostępu do wyniku elementu zanim jest wiadomo, że został on zakończony, mogą spowodować wystąpienie wyjątku lub uszkodzenia. Zignorowanie takiego elementu ValueTask prawdopodobnie wskazuje na usterkę funkcjonalną i może obniżyć wydajność. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index f73645c162..f56187c75f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2237,6 +2237,21 @@ Se possível, considere usar o controle de acesso baseado em função do Azure em vez de uma SAS (Assinatura de Acesso Compartilhado). Se você ainda precisar usar uma SAS, especifique SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. ValueTasks retornados de invocações de membro devem ser aguardados diretamente. Tentativas de consumir um ValueTask várias vezes ou de acessar diretamente um resultado antes que esteja sabidamente concluído podem resultar em exceção ou dano. Ignorar um ValueTask é provavelmente uma indicação de um bug funcional e pode degradar o desempenho. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 3b853eaf7e..5f75bc3e08 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2237,6 +2237,21 @@ Если возможно, попробуйте использовать управление доступом на основе ролей Azure, а не подписанный URL-адрес (SAS). Если все-таки требуется использовать SAS, укажите SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. ValueTask, возвращаемые в результате вызова членов, должны ожидаться напрямую. Попытки использовать ValueTask несколько раз или получить прямой доступ к результату, прежде чем он будет получен, могут привести к выдаче исключения или повреждению данных. Пропуск такого ValueTask, вероятно, свидетельствует о функциональной ошибке и может привести к снижению производительности. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index e85849b540..40a280c256 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2237,6 +2237,21 @@ Mümkünse Paylaşılan Erişim İmzası (SAS) yerine Azure'un rol tabanlı erişim denetimini kullanmayı düşünün. Yine de SAS kullanmanız gerekiyorsa, SharedAccessProtocol.HttpsOnly belirtin. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Üye çağırmalarından döndürülen ValueTask'lerin doğrudan beklenmesi amaçlanır. Bir ValueTask'ı birden çok kez kullanmak ya da tamamlandığı bilinmeden önce birinin sonucuna doğrudan erişmek, bir özel duruma veya bozulmaya neden olabilir. Bu tür bir ValueTask'ın yoksayılması, büyük olasılıkla bir işlev hatasının göstergesidir ve performansı düşürebilir. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 2548f9f6f3..72fb48bcba 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2237,6 +2237,21 @@ 如果可能,请考虑使用 Azure 基于角色的访问控制,而不是共享访问签名(SAS)。如果仍需使用 SAS,请指定 SharedAccessProtocol.HttpsOnly。 + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. 应直接等待从成员调用返回的 ValueTask。尝试多次使用 ValueTask 或在已知完成前直接访问结果可能导致异常或损坏。 忽略此类 ValueTask 很可能表示存在功能 bug,可能会降低性能。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 1aebe6df07..3783ef55bb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2237,6 +2237,21 @@ 如果可行的話,請考慮從共用存取簽章 (SAS) 改為使用 Azure 的角色型存取控制。如果您仍需要使用 SAS,請指定 SharedAccessProtocol.HttpsOnly。 + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. 應直接等候從成員引動過程傳回的 ValueTask。若在已得知完成 ValueTask 之前,嘗試多次使用 ValueTask 或直接存取其結果,可能會導致發生例外狀況或損毀。忽略這類 ValueTask 可能表示發生功能性 Bug,而且可能會降低效能。 diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 8f68eb73f0..0d71a5a214 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -2,19 +2,728 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat, - Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcatFixer>; + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpUseSpanBasedStringConcat, + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpUseSpanBasedStringConcatFixer>; using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< - Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat, - Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcatFixer>; + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicUseSpanBasedStringConcat, + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicUseSpanBasedStringConcatFixer>; namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests { public class UseSpanBasedStringConcatTests { + #region Reports Diagnostic + public static IEnumerable Data_SingleViolationInOneBlock_CS + { + get + { + yield return new[] + { + @"var _ = {|#0:foo.Substring(1) + bar|};", + @"var _ = string.Concat(foo.AsSpan(1), bar);" + }; + yield return new[] + { + @"var _ = {|#0:foo.Substring(1, 2) + bar|};", + @"var _ = string.Concat(foo.AsSpan(1, 2), bar);" + }; + yield return new[] + { + @"var _ = {|#0:foo + bar.Substring(1)|};", + @"var _ = string.Concat(foo, bar.AsSpan(1));" + }; + yield return new[] + { + @"var _ = {|#0:foo + bar.Substring(1) + baz|};", + @"var _ = string.Concat(foo, bar.AsSpan(1), baz);" + }; + yield return new[] + { + @"var _ = {|#0:foo.Substring(1) + bar.Substring(1) + baz.Substring(1)|};", + @"var _ = string.Concat(foo.AsSpan(1), bar.AsSpan(1), baz.AsSpan(1));" + }; + yield return new[] + { + @"var _ = {|#0:foo.Substring(1, 2) + bar + baz + baz.Substring(1, 2)|};", + @"var _ = string.Concat(foo.AsSpan(1, 2), bar, baz, baz.AsSpan(1, 2));" + }; + yield return new[] + { + @"Consume({|#0:foo + bar.Substring(1, 2)|});", + @"Consume(string.Concat(foo, bar.AsSpan(1, 2)));" + }; + yield return new[] + { + @"var _ = Fwd({|#0:foo.Substring(1) + bar|});", + @"var _ = Fwd(string.Concat(foo.AsSpan(1), bar));" + }; + yield return new[] + { + @"var _ = Fwd({|#0:foo.Substring(1) + bar.Substring(1)|});", + @"var _ = Fwd(string.Concat(foo.AsSpan(1), bar.AsSpan(1)));" + }; + } + } + + [Theory] + [MemberData(nameof(Data_SingleViolationInOneBlock_CS))] + public Task SingleViolationInOneBlock_ReportedAndFixed_CS(string testStatements, string fixedStatements) + { + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(testStatements), + FixedCode = CSUsings + CSWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + public static IEnumerable Data_SingleViolationInOneBlock_VB + { + get + { + yield return new[] + { + @"Dim s = {|#0:foo.Substring(1) & bar|}", + @"Dim s = String.Concat(foo.AsSpan(1), bar)" + }; + yield return new[] + { + @"Dim s = {|#0:foo.Substring(1, 2) & bar|}", + @"Dim s = String.Concat(foo.AsSpan(1, 2), bar)" + }; + yield return new[] + { + @"Dim s = {|#0:foo & bar.Substring(1)|}", + @"Dim s = String.Concat(foo, bar.AsSpan(1))" + }; + yield return new[] + { + @"Dim s = {|#0:foo & bar.Substring(1) & baz|}", + @"Dim s = String.Concat(foo, bar.AsSpan(1), baz)" + }; + yield return new[] + { + @"Dim s = {|#0:foo.Substring(1) & bar.Substring(1) & baz.Substring(1)|}", + @"Dim s = String.Concat(foo.AsSpan(1), bar.AsSpan(1), baz.AsSpan(1))" + }; + yield return new[] + { + @"Dim s = {|#0:foo.Substring(1, 2) & bar & baz & baz.Substring(1, 2)|}", + @"Dim s = String.Concat(foo.AsSpan(1, 2), bar, baz, baz.AsSpan(1, 2))" + }; + yield return new[] + { + @"Consume({|#0:foo & bar.Substring(1, 2)|})", + @"Consume(String.Concat(foo, bar.AsSpan(1, 2)))" + }; + yield return new[] + { + @"Dim s = Fwd({|#0:foo.Substring(1) & bar|})", + @"Dim s = Fwd(String.Concat(foo.AsSpan(1), bar))" + }; + yield return new[] + { + @"Dim s = Fwd({|#0:foo.Substring(1) & bar.Substring(1)|})", + @"Dim s = Fwd(String.Concat(foo.AsSpan(1), bar.AsSpan(1)))" + }; + } + } + + [Theory] + [MemberData(nameof(Data_SingleViolationInOneBlock_VB))] + public Task SingleViolationInOneBlock_ReportedAndFixed_VB(string testStatement, string fixedStatement) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(testStatement), + FixedCode = VBUsings + VBWithBody(fixedStatement), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + public static IEnumerable Data_MultipleViolationsInOneBlock_CS + { + get + { + yield return new object[] + { + @" +string alpha = {|#0:foo.Substring(1, 2) + bar.Substring(1) + baz|}; +string bravo = {|#1:foo + bar.Substring(3) + baz.Substring(1, 2)|}; +string charlie = {|#2:foo + bar + baz.Substring(1, 2)|}; +string delta = {|#3:foo.Substring(1) + bar.Substring(1) + baz.Substring(1, 2) + foo.Substring(1, 2)|};", + @" +string alpha = string.Concat(foo.AsSpan(1, 2), bar.AsSpan(1), baz); +string bravo = string.Concat(foo, bar.AsSpan(3), baz.AsSpan(1, 2)); +string charlie = string.Concat(foo, bar, baz.AsSpan(1, 2)); +string delta = string.Concat(foo.AsSpan(1), bar.AsSpan(1), baz.AsSpan(1, 2), foo.AsSpan(1, 2));", + new[] { 0, 1, 2, 3 } + }; + yield return new object[] + { + @"Consume({|#0:foo.Substring(1) + bar|}, {|#1:foo + bar.Substring(1)|});", + @"Consume(string.Concat(foo.AsSpan(1), bar), string.Concat(foo, bar.AsSpan(1)));", + new[] { 0, 1 } + }; + yield return new object[] + { + @"Consume(Fwd({|#0:foo.Substring(1) + bar|}), Fwd({|#1:foo + bar.Substring(1)|}));", + @"Consume(Fwd(string.Concat(foo.AsSpan(1), bar)), Fwd(string.Concat(foo, bar.AsSpan(1))));", + new[] { 0, 1 } + }; + } + } + + [Theory] + [MemberData(nameof(Data_MultipleViolationsInOneBlock_CS))] + public Task MultipleViolationsInOneBlock_AreReportedAndFixed_CS(string testStatements, string fixedStatements, int[] locations) + { + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(testStatements), + FixedCode = CSUsings + CSWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyCS.Diagnostic(Rule).WithLocation(x))); + return test.RunAsync(); + } + + public static IEnumerable Data_MultipleViolationsInOneBlock_VB + { + get + { + yield return new object[] + { + @" +Dim alpha = {|#0:foo.Substring(1, 2) & bar.Substring(1) & baz|} +Dim bravo = {|#1:foo & bar.Substring(3) & baz.Substring(1, 2)|} +Dim charlie = {|#2:foo & bar & baz.Substring(1, 2)|} +Dim delta = {|#3:foo.Substring(1) & bar.Substring(1) & baz.Substring(1, 2) & foo.Substring(1, 2)|}", + @" +Dim alpha = String.Concat(foo.AsSpan(1, 2), bar.AsSpan(1), baz) +Dim bravo = String.Concat(foo, bar.AsSpan(3), baz.AsSpan(1, 2)) +Dim charlie = String.Concat(foo, bar, baz.AsSpan(1, 2)) +Dim delta = String.Concat(foo.AsSpan(1), bar.AsSpan(1), baz.AsSpan(1, 2), foo.AsSpan(1, 2))", + new[] { 0, 1, 2, 3 } + }; + yield return new object[] + { + @"Consume({|#0:foo.Substring(1) & bar|}, {|#1:foo & bar.Substring(1)|})", + @"Consume(String.Concat(foo.AsSpan(1), bar), String.Concat(foo, bar.AsSpan(1)))", + new[] { 0, 1 } + }; + yield return new object[] + { + @"Consume(Fwd({|#0:foo.Substring(1) & bar|}), Fwd({|#1:foo & bar.Substring(1)|}))", + @"Consume(Fwd(String.Concat(foo.AsSpan(1), bar)), Fwd(String.Concat(foo, bar.AsSpan(1))))", + new[] { 0, 1 } + }; + } + } + + [Theory] + [MemberData(nameof(Data_MultipleViolationsInOneBlock_VB))] + public Task MultipleViolationsInOneBlock_AreReportedAndFixed_VB(string testStatements, string fixedStatements, int[] locations) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(testStatements), + FixedCode = VBUsings + VBWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + }; + test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyVB.Diagnostic(Rule).WithLocation(x))); + return test.RunAsync(); + } + + public static IEnumerable Data_NestedViolations_CS + { + get + { + yield return new object[] + { + @" +Consume({|#0:Fwd({|#1:foo + bar.Substring(1)|}) + baz.Substring(1)|});", + @" +Consume(string.Concat(Fwd(string.Concat(foo, bar.AsSpan(1))), baz.AsSpan(1)));", + new[] { 0, 1 }, + -4 + }; + yield return new object[] + { + @" +var _ = {|#0:Fwd({|#1:foo.Substring(1) + bar.Substring(1)|}) + Fwd({|#2:foo.Substring(1) + bar|}).Substring(1) + Fwd({|#3:foo + bar.Substring(1)|})|};", + @" +var _ = string.Concat(Fwd(string.Concat(foo.AsSpan(1), bar.AsSpan(1))), Fwd(string.Concat(foo.AsSpan(1), bar)).AsSpan(1), Fwd(string.Concat(foo, bar.AsSpan(1))));", + new[] { 0, 1, 2, 3 }, + -4 + }; + } + } + + [Theory] + [MemberData(nameof(Data_NestedViolations_CS))] + public Task NestedViolations_AreReportedAndFixed_CS(string testStatements, string fixedStatements, int[] locations, int? iterations = null) + { + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(testStatements), + FixedCode = CSUsings + CSWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + NumberOfIncrementalIterations = iterations, + NumberOfFixAllInDocumentIterations = iterations, + NumberOfFixAllIterations = iterations + }; + test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyCS.Diagnostic(Rule).WithLocation(x))); + return test.RunAsync(); + } + + public static IEnumerable Data_NestedViolations_VB + { + get + { + yield return new object[] + { + @" +Consume({|#0:Fwd({|#1:foo + bar.Substring(1)|}) + baz.Substring(1)|})", + @" +Consume(String.Concat(Fwd(String.Concat(foo, bar.AsSpan(1))), baz.AsSpan(1)))", + new[] { 0, 1 } + }; + yield return new object[] + { + @" +Dim s = {|#0:Fwd({|#1:foo.Substring(1) & bar.Substring(1)|}) & Fwd({|#2:foo.Substring(1) & bar|}).Substring(1) & Fwd({|#3:foo & bar.Substring(1)|})|}", + @" +Dim s = String.Concat(Fwd(String.Concat(foo.AsSpan(1), bar.AsSpan(1))), Fwd(String.Concat(foo.AsSpan(1), bar)).AsSpan(1), Fwd(String.Concat(foo, bar.AsSpan(1))))", + new[] { 0, 1, 2, 3 } + }; + } + } + + [Theory] + [MemberData(nameof(Data_NestedViolations_VB))] + public Task NestedViolations_AreReportedAndFixed_VB(string testStatements, string fixedStatements, int[] locations) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(testStatements), + FixedCode = VBUsings + VBWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + NumberOfIncrementalIterations = -10, + NumberOfFixAllIterations = -10, + NumberOfFixAllInDocumentIterations = -10 + }; + test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyVB.Diagnostic(Rule).WithLocation(x))); + return test.RunAsync(); + } + + [Fact] + public Task MissingImports_AreAdded_CS() + { + var test = new VerifyCS.Test + { + TestCode = CSWithBody(@"var _ = {|#0:foo + bar.Substring(1)|};"), + FixedCode = $"\r\n{CSUsings}\r\n" + CSWithBody(@"var _ = string.Concat(foo, bar.AsSpan(1));"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Fact] + public Task MissingImports_AreAdded_VB() + { + var test = new VerifyVB.Test + { + TestCode = VBWithBody(@"Dim s = {|#0:foo & bar.Substring(1)|}"), + FixedCode = $"\r\n{VBUsings}\r\n" + VBWithBody(@"Dim s = String.Concat(foo, bar.AsSpan(1))"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(@"Substring(startIndex: 1)", @"AsSpan(start: 1)")] + [InlineData(@"Substring(startIndex: 1, length: 2)", @"AsSpan(start: 1, length: 2)")] + [InlineData(@"Substring(1, length: 2)", @"AsSpan(1, length: 2)")] + [InlineData(@"Substring(startIndex: 1, 2)", @"AsSpan(start: 1, 2)")] + [InlineData(@"Substring(length: 2, startIndex: 1)", @"AsSpan(length: 2, start: 1)")] + public Task NamedSubstringArguments_ArePreserved_CS(string substring, string asSpan) + { + string testStatements = $@"var s = {{|#0:foo.{substring} + bar|}};"; + string fixedStatements = $@"var s = string.Concat(foo.{asSpan}, bar);"; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(testStatements), + FixedCode = CSUsings + CSWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(@"Substring(startIndex:=1)", @"AsSpan(start:=1)")] + [InlineData(@"Substring(startIndex:=1, length:=2)", @"AsSpan(start:=1, length:=2)")] + [InlineData(@"Substring(1, length:=2)", @"AsSpan(1, length:=2)")] + [InlineData(@"Substring(startIndex:=1, 2)", @"AsSpan(start:=1, 2)")] + [InlineData(@"Substring(length:=2, startIndex:=1)", @"AsSpan(length:=2, start:=1)")] + public Task NamedSubstringArguments_ArePreserved_VB(string substring, string asSpan) + { + string testStatements = $@"Dim s = {{|#0:foo.{substring} & bar|}}"; + string fixedStatements = $@"Dim s = String.Concat(foo.{asSpan}, bar)"; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(testStatements), + FixedCode = VBUsings + VBWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + public static IEnumerable Data_NonStringOperands_CS + { + get + { + yield return new[] + { + @"foo.Substring(1) + count", + @"string.Concat(foo.AsSpan(1), count.ToString())" + }; + yield return new[] + { + @"count + foo.Substring(1)", + @"string.Concat(count.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"thing + foo.Substring(1)", + @"string.Concat(thing?.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"foo.Substring(1) + thing", + @"string.Concat(foo.AsSpan(1), thing?.ToString())" + }; + yield return new[] + { + @"maybe + foo.Substring(1)", + @"string.Concat(maybe?.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"thing + foo.Substring(1, 2) + maybe", + @"string.Concat(thing?.ToString(), foo.AsSpan(1, 2), maybe?.ToString())" + }; + yield return new[] + { + @"foo + 17 + bar.Substring(1)", + @"string.Concat(foo, 17.ToString(), bar.AsSpan(1))" + }; + yield return new[] + { + @"foo.Substring(1, 2) + 17", + @"string.Concat(foo.AsSpan(1, 2), 17.ToString())" + }; + yield return new[] + { + @"foo + 3.14159 + bar.Substring(1)", + @"string.Concat(foo, 3.14159.ToString(), bar.AsSpan(1))" + }; + yield return new[] + { + @"3.14159 + foo.Substring(1) + 6.02e23", + @"string.Concat(3.14159.ToString(), foo.AsSpan(1), 6.02e23.ToString())" + }; + } + } + + [Theory] + [MemberData(nameof(Data_NonStringOperands_CS))] + public Task NonStringOperands_AreConvertedToString_CS(string testExpression, string fixedExpression) + { + string format = @" +int count = 11; +object thing = new object(); +TimeSpan? maybe = null; +string s = {0};"; + var culture = CultureInfo.InvariantCulture; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(string.Format(culture, format, $"{{|#0:{testExpression}|}}")), + FixedCode = CSUsings + CSWithBody(string.Format(culture, format, fixedExpression)), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + public static IEnumerable Data_NonStringOperands_VB + { + get + { + yield return new[] + { + @"foo.Substring(1) & count", + @"String.Concat(foo.AsSpan(1), count.ToString())" + }; + yield return new[] + { + @"count & foo.Substring(1)", + @"String.Concat(count.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"thing & foo.Substring(1)", + @"String.Concat(thing?.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"foo.Substring(1) & thing", + @"String.Concat(foo.AsSpan(1), thing?.ToString())" + }; + yield return new[] + { + @"maybe & foo.Substring(1)", + @"String.Concat(maybe?.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"thing & foo.Substring(1, 2) & maybe", + @"String.Concat(thing?.ToString(), foo.AsSpan(1, 2), maybe?.ToString())" + }; + yield return new[] + { + @"foo & 17 & bar.Substring(1)", + @"String.Concat(foo, 17.ToString(), bar.AsSpan(1))" + }; + yield return new[] + { + @"foo.Substring(1, 2) & 17", + @"String.Concat(foo.AsSpan(1, 2), 17.ToString())" + }; + yield return new[] + { + @"foo & 3.14159 & bar.Substring(1)", + @"String.Concat(foo, 3.14159.ToString(), bar.AsSpan(1))" + }; + yield return new[] + { + @"3.14159 & foo.Substring(1) & 6.02e23", + @"String.Concat(3.14159.ToString(), foo.AsSpan(1), 6.02e23.ToString())" + }; + } + } + + [Theory] + [MemberData(nameof(Data_NonStringOperands_VB))] + public Task NonStringOperands_AreConvertedToString_VB(string testExpression, string fixedExpression) + { + string format = @" +Dim count As Integer = 11 +Dim thing As Object = New Object() +Dim maybe As TimeSpan? = Nothing +Dim s = {0}"; + var culture = CultureInfo.InvariantCulture; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(string.Format(culture, format, $"{{|#0:{testExpression}|}}")), + FixedCode = VBUsings + VBWithBody(string.Format(culture, format, fixedExpression)), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + #endregion + + #region No Diagnostic + [Theory] + [InlineData("foo.Substring(1) + foo + foo + bar + baz")] + [InlineData("foo + foo.Substring(1) + bar + baz + baz")] + [InlineData("foo.Substring(1) + bar.Substring(1) + baz.Substring(1) + bar.Substring(1) + foo.Substring(1)")] + [InlineData("foo.Substring(1) + bar + baz + foo.Substring(1) + bar + baz")] + [InlineData("foo + bar.Substring(1) + baz + foo + bar.Substring(1) + baz")] + [InlineData("foo.Substring(1) + bar + baz.Substring(1) + foo.Substring(1) + bar + baz.Substring(1)")] + public Task TooManyArguments_NoDiagnostic_CS(string expression) + { + string statements = $@"var s = {expression};"; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData("foo.Substring(1) & foo & foo & bar & baz")] + [InlineData("foo & foo.Substring(1) & bar & baz & baz")] + [InlineData("foo.Substring(1) & bar.Substring(1) & baz.Substring(1) & bar.Substring(1) & foo.Substring(1)")] + [InlineData("foo.Substring(1) & bar & baz & foo.Substring(1) & bar & baz")] + [InlineData("foo & bar.Substring(1) & baz & foo & bar.Substring(1) & baz")] + [InlineData("foo.Substring(1) & bar & baz.Substring(1) & foo.Substring(1) & bar & baz.Substring(1)")] + public Task TooManyArguments_NoDiagnostic_VB(string expression) + { + string statements = $@"Dim s = {expression}"; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData("foo + bar")] + [InlineData("foo + bar + baz")] + [InlineData("foo + bar.ToUpper()")] + [InlineData("foo.ToLower() + bar")] + public Task NoSubstringInvocations_NoDiagnostic_CS(string expression) + { + string statements = $@"var s = {expression};"; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData("foo & bar")] + [InlineData("foo & bar & baz")] + [InlineData("foo & bar.ToUpper()")] + [InlineData("foo.ToLower() & bar")] + public Task NoSubstringInvocations_NoDiagnostic_VB(string expression) + { + string statements = $@"Dim s = {expression}"; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + // No VB case because VB can't overload operators. + [Theory] + [InlineData(@"foo.Substring(1) + evil")] + [InlineData(@"evil + foo.Substring(1)")] + [InlineData(@"foo + evil + bar.Substring(1)")] + [InlineData(@"foo.Substring(1) + evil + bar")] + [InlineData(@"foo.Substring(1) + evil + bar + baz")] + [InlineData(@"foo + bar + evil + baz.Substring(1)")] + [InlineData(@"foo + evil + bar.Substring(1) + evil")] + [InlineData(@"foo + evil + bar.Substring(1) + evil + baz.Substring(1)")] + public Task OverloadedAddOperator_NoDiagnostic(string expression) + { + string statements = $@" +var evil = new EvilOverloads(); +var e = {expression};"; + + var test = new VerifyCS.Test + { + TestState = + { + Sources = + { + EvilOverloads, + CSUsings + CSWithBody(statements) + } + }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + #endregion + + #region Helpers + private static DiagnosticDescriptor Rule => UseSpanBasedStringConcat.Rule; + private const string CSUsings = @"using System;"; + private const string VBUsings = @"Imports System"; + private const string EvilOverloads = @" +public class EvilOverloads +{ + public static EvilOverloads operator +(EvilOverloads left, string right) => left; + public static EvilOverloads operator +(string left, EvilOverloads right) => right; + public static EvilOverloads operator +(EvilOverloads left, EvilOverloads right) => left; +}"; + + private static string CSWithBody(string statements) + { + return @" +public class Testopolis +{ + private void Consume(string consumed) { } + private void Consume(string s1, string s2) { } + private void Consume(string s1, string s2, string s3) { } + private string Fwd(string arg) => arg; + private string Transform(string first, string second) => second; + private string Transform(string first, string second, string third) => first; + private string Produce() => ""Hello World""; + + public void FrobThem(string foo, string bar, string baz) + { +" + IndentLines(statements, " ") + @" + } +}"; + } + + private static string VBWithBody(string statements) + { + return @" +Public Class Testopolis + Private Sub Consume(consumed As String) + End Sub + Private Sub Consume(s1 As String, s2 As String) + End Sub + Private Sub Consume(s1 As String, s2 As String, s3 As String) + End Sub + Private Function Fwd(arg As String) As String + Return arg + End Function + Private Function Transform(first As String, second As String) As String + Return second + End Function + Private Function Transform(first As String, second As String, third As String) As String + Return first + End Function + Private Function Produce() As String + Return ""Hello World"" + End Function + + Public Sub FrobThem(foo As String, bar As String, baz As String) +" + IndentLines(statements, " ") + @" + End Sub +End Class"; + } + + private static string IndentLines(string body, string indent) + { + return indent + body.TrimStart().Replace("\r\n", "\r\n" + indent, StringComparison.Ordinal); + } + #endregion } } diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb new file mode 100644 index 0000000000..7e81120772 --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb @@ -0,0 +1,65 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.Operations +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.NetCore.Analyzers.Runtime + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + + + Public NotInheritable Class BasicUseSpanBasedStringConcatFixer : Inherits UseSpanBasedStringConcatFixer + + Private Protected Overrides Function ReplaceInvocationMethodName(generator As SyntaxGenerator, invocationSyntax As SyntaxNode, newName As String) As SyntaxNode + + Dim cast = DirectCast(invocationSyntax, InvocationExpressionSyntax) + Dim memberAccessSyntax = DirectCast(cast.Expression, MemberAccessExpressionSyntax) + Dim oldNameSyntax = memberAccessSyntax.Name + Dim newNameSyntax = generator.IdentifierName(newName).WithTriviaFrom(oldNameSyntax) + Return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax) + End Function + + Private Protected Overrides Function GetOperatorToken(binaryOperation As IBinaryOperation) As SyntaxToken + + Dim syntax = DirectCast(binaryOperation.Syntax, BinaryExpressionSyntax) + Return syntax.OperatorToken + End Function + + Private Protected Overrides Function IsSystemNamespaceImported(namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean + + For Each node As SyntaxNode In namespaceImports + Dim importsStatement = TryCast(node, ImportsStatementSyntax) + If importsStatement Is Nothing Then + Continue For + End If + For Each importsClause As ImportsClauseSyntax In importsStatement.ImportsClauses + Dim simpleClause = TryCast(importsClause, SimpleImportsClauseSyntax) + Dim identifierName = TryCast(simpleClause?.Name, IdentifierNameSyntax) + If identifierName Is Nothing Then + Continue For + End If + If identifierName.Identifier.ValueText = NameOf(System) Then + Return True + End If + Next + Next + Return False + End Function + + Private Protected Overrides Function IsNamedArgument(argument As IArgumentOperation) As Boolean + Return DirectCast(argument.Syntax, ArgumentSyntax).IsNamed + End Function + + Private Protected Overrides Function CreateConditionalToStringInvocation(receiverExpression As SyntaxNode) As SyntaxNode + + Dim expression = DirectCast(receiverExpression, ExpressionSyntax) + Dim memberAccessExpression = SyntaxFactory.SimpleMemberAccessExpression(SyntaxFactory.IdentifierName(ToStringName)) + Dim invocationExpression = SyntaxFactory.InvocationExpression(memberAccessExpression) + Return SyntaxFactory.ConditionalAccessExpression(expression.WithoutTrivia(), invocationExpression).WithTriviaFrom(expression) + End Function + End Class +End Namespace + diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb new file mode 100644 index 0000000000..7fd117bc96 --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb @@ -0,0 +1,18 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.NetCore.Analyzers.Runtime +Imports Microsoft.CodeAnalysis.Operations +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + + + Public NotInheritable Class BasicUseSpanBasedStringConcat : Inherits UseSpanBasedStringConcat + + Private Protected Overrides Function IsStringConcatOperation(binaryOperation As IBinaryOperation) As Boolean + Return binaryOperation.OperatorKind = BinaryOperatorKind.Concatenate + End Function + End Class +End Namespace + diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 2a45fad59c..a6033ad82a 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -218,6 +218,7 @@ internal static class WellKnownTypeNames public const string SystemLinqQueryable = "System.Linq.Queryable"; public const string SystemMarshalByRefObject = "System.MarshalByRefObject"; public const string SystemMemory1 = "System.Memory`1"; + public const string SystemMemoryExtensions = "System.MemoryExtensions"; public const string SystemNetHttpHttpClient = "System.Net.Http.HttpClient"; public const string SystemNetHttpHttpClientHandler = "System.Net.Http.HttpClientHandler"; public const string SystemNetHttpWinHttpHandler = "System.Net.Http.WinHttpHandler"; From 0fe5cce98e84e3b23ad151fd20b15e450b7ed0be Mon Sep 17 00:00:00 2001 From: NewellClark Date: Tue, 2 Feb 2021 12:06:09 -0500 Subject: [PATCH 025/224] Analyzer and fixer working --- .../CSharpUseSpanBasedStringConcat.Fixer.cs | 33 +++-- .../Runtime/CSharpUseSpanBasedStringConcat.cs | 23 ++- .../MicrosoftNetCoreAnalyzersResources.resx | 6 +- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 139 +++++++++--------- .../Runtime/UseSpanBasedStringConcat.cs | 69 ++++----- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 12 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 12 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 12 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 12 +- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 ++ .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 38 +++++ src/NetAnalyzers/RulesMissingDocumentation.md | 1 + .../Runtime/UseSpanBasedStringConcatTests.cs | 112 +++++++++++++- .../BasicUseSpanBasedStringConcat.Fixer.vb | 34 +++-- .../Runtime/BasicUseSpanBasedStringConcat.vb | 24 ++- 24 files changed, 416 insertions(+), 231 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs index 23e54c8576..8289df89bf 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using Analyzer.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; @@ -22,12 +23,6 @@ private protected override SyntaxNode ReplaceInvocationMethodName(SyntaxGenerato return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax); } - private protected override SyntaxToken GetOperatorToken(IBinaryOperation binaryOperation) - { - var syntax = (BinaryExpressionSyntax)binaryOperation.Syntax; - return syntax.OperatorToken; - } - private protected override bool IsSystemNamespaceImported(IReadOnlyList namespaceImports) { foreach (var import in namespaceImports) @@ -38,18 +33,28 @@ private protected override bool IsSystemNamespaceImported(IReadOnlyListThis call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title + Use span-based string.Concat \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 0b1c09cbdc..63405c5fd3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -16,27 +16,31 @@ using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat.RequiredSymbols; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.NetCore.Analyzers.Runtime { public abstract class UseSpanBasedStringConcatFixer : CodeFixProvider { - private const string AsSpanName = nameof(MemoryExtensions.AsSpan); - private const string AsSpanStartParameterName = "start"; + private protected const string AsSpanName = nameof(MemoryExtensions.AsSpan); + private protected const string AsSpanStartParameterName = "start"; private protected const string ToStringName = nameof(ToString); private protected abstract SyntaxNode ReplaceInvocationMethodName(SyntaxGenerator generator, SyntaxNode invocationSyntax, string newName); - private protected abstract SyntaxToken GetOperatorToken(IBinaryOperation binaryOperation); - private protected abstract bool IsSystemNamespaceImported(IReadOnlyList namespaceImports); - private protected abstract bool IsNamedArgument(IArgumentOperation argument); + /// Invoke ToString with the Elvis operator. + private protected abstract SyntaxNode GenerateConditionalToStringInvocationExpression(SyntaxNode expression); /// - /// Invokes on the specified expression using the Elvis operator. + /// Remove the built in implicit conversion to object when a non-string operand is concatenated in C#. + /// In Visual Basic, the implicit conversion can be to string or object. + /// User-defined conversions are not removed. /// - private protected abstract SyntaxNode CreateConditionalToStringInvocation(SyntaxNode receiverExpression); + private protected abstract IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand); + + private protected abstract bool IsNamedArgument(IArgumentOperation argumentOperation); public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); @@ -49,15 +53,15 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) var compilation = model.Compilation; SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - // Bail out early if we're missing anything we need. if (!RequiredSymbols.TryGetSymbols(compilation, out var symbols)) return; if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode concatExpressionSyntax) return; - if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind != symbols.ConcatOperatorKind) + // OperatorKind will be BinaryOperatorKind.Concatenate, even when '+' is used instead of '&' in Visual Basic. + if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind is not (BinaryOperatorKind.Add or BinaryOperatorKind.Concatenate)) return; - var operands = UseSpanBasedStringConcat.FlattenBinaryOperationChain(concatOperation); + var operands = UseSpanBasedStringConcat.FlattenBinaryOperation(concatOperation); // Bail out if we don't have a long enough span-based string.Concat overload. if (!symbols.TryGetRoscharConcatMethodWithArity(operands.Length, out IMethodSymbol? roscharConcatMethod)) return; @@ -75,16 +79,25 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - // Save leading and trailing trivia so it can be attached to the outside of the 'string.Concat(...)' invocation expression. + SyntaxNode stringTypeNameSyntax = generator.TypeExpressionForStaticMemberAccess(symbols.StringType); + SyntaxNode concatMemberAccessSyntax = generator.MemberAccessExpression(stringTypeNameSyntax, roscharConcatMethod.Name); + + // Save leading and trailing trivia so it can be attached to the outside of the string.Concat invocation node. var leadingTrivia = operands.First().Syntax.GetLeadingTrivia(); var trailingTrivia = operands.Last().Syntax.GetTrailingTrivia(); - SyntaxNode stringTypeNameSyntax = generator.TypeExpressionForStaticMemberAccess(symbols.StringType); - SyntaxNode concatMemberAccessSyntax = generator.MemberAccessExpression(stringTypeNameSyntax, roscharConcatMethod.Name); - var arguments = GenerateConcatMethodArguments(symbols, generator, operands); - SyntaxNode concatMethodInvocationSyntax = generator.InvocationExpression(concatMemberAccessSyntax, arguments) + var arguments = ImmutableArray.CreateBuilder(operands.Length); + foreach (var operand in operands) + arguments.Add(ConvertOperandToArgument(symbols, generator, operand)); + + // Strip off leading and trailing trivia from first and last operand nodes, respectively, and + // reattach it to the outside of the newly-created string.Concat invocation node. + arguments[0] = arguments[0].WithoutLeadingTrivia(); + arguments[^1] = arguments[^1].WithoutTrailingTrivia(); + SyntaxNode concatMethodInvocationSyntax = generator.InvocationExpression(concatMemberAccessSyntax, arguments.MoveToImmutable()) .WithLeadingTrivia(leadingTrivia) .WithTrailingTrivia(trailingTrivia); + var newRoot = generator.ReplaceNode(root, concatExpressionSyntax, concatMethodInvocationSyntax); // Make sure 'System' namespace is imported. @@ -99,78 +112,64 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken } } - private ImmutableArray GenerateConcatMethodArguments(in RequiredSymbols symbols, SyntaxGenerator generator, ImmutableArray operands) + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + private SyntaxNode ConvertOperandToArgument(in RequiredSymbols symbols, SyntaxGenerator generator, IOperation operand) { - var builder = ImmutableArray.CreateBuilder(operands.Length); - foreach (IOperation operand in operands) - { - // Convert 'Substring' invocations into 'AsSpan' invocations. - if (WalkDownImplicitConversion(operand) is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) - { - SyntaxNode newInvocationSyntax = invocation.Syntax; + var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operand); - // Convert 'Substring' named-arguments to equivalent 'AsSpan' named arguments. - IArgumentOperation? namedStartIndexArgument = GetNamedStartIndexArgumentOrDefault(symbols, invocation); - if (namedStartIndexArgument is not null) - { - SyntaxNode renamedSubstringArgumentSyntax = generator.Argument(AsSpanStartParameterName, RefKind.None, namedStartIndexArgument.Value.Syntax); - newInvocationSyntax = generator.ReplaceNode(newInvocationSyntax, namedStartIndexArgument.Syntax, renamedSubstringArgumentSyntax); - } + // Convert substring invocations to equivalent AsSpan invocation. + if (value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) + { + SyntaxNode invocationSyntax = invocation.Syntax; - // Replace 'Substring' identifier with 'AsSpan', leaving the rest of the node (including trivia) intact. - newInvocationSyntax = ReplaceInvocationMethodName(generator, newInvocationSyntax, AsSpanName); - builder.Add(generator.Argument(newInvocationSyntax)); + // Swap out parameter names if named-arguments are used. + if (TryGetNamedStartIndexArgument(symbols, invocation, out var namedStartIndexArgument)) + { + var renamedArgumentSyntax = generator.Argument(AsSpanStartParameterName, RefKind.None, namedStartIndexArgument.Value.Syntax); + invocationSyntax = generator.ReplaceNode(invocationSyntax, namedStartIndexArgument.Syntax, renamedArgumentSyntax); + } + var asSpanInvocationSyntax = ReplaceInvocationMethodName(generator, invocationSyntax, AsSpanName); + return generator.Argument(asSpanInvocationSyntax); + } + else if (value.Type.SpecialType == SpecialType.System_String) + { + return generator.Argument(value.Syntax); + } + // If the operand is a non-string type, we need to invoke ToString() on it to + // prevent overload resolution from choosing an object-based string.Concat overload. + else + { + // Invoke ToString with Elvis operator if receiver could be null. + if (value.Type.IsReferenceTypeOrNullableValueType()) + { + SyntaxNode conditionalAccessSyntax = GenerateConditionalToStringInvocationExpression(value.Syntax); + return generator.Argument(conditionalAccessSyntax); } else { - IOperation value = WalkDownImplicitConversion(operand); - if (value.Type.SpecialType == SpecialType.System_String) - { - builder.Add(generator.Argument(value.Syntax)); - } - else - { - SyntaxNode newValueSyntax; - if (value.Type.IsReferenceTypeOrNullableValueType()) - { - newValueSyntax = CreateConditionalToStringInvocation(value.Syntax); - } - else - { - SyntaxNode toStringMemberAccessSyntax = generator.MemberAccessExpression(value.Syntax.WithoutTrivia(), ToStringName); - newValueSyntax = generator.InvocationExpression(toStringMemberAccessSyntax, Array.Empty()) - .WithTriviaFrom(value.Syntax); - } - builder.Add(generator.Argument(newValueSyntax)); - } + SyntaxNode memberAccessSyntax = generator.MemberAccessExpression(value.Syntax.WithoutTrivia(), ToStringName); + SyntaxNode toStringInvocationSyntax = generator.InvocationExpression(memberAccessSyntax, Array.Empty()); + return generator.Argument(toStringInvocationSyntax).WithTriviaFrom(value.Syntax); } } - builder[0] = builder[0].WithoutLeadingTrivia(); - builder[^1] = builder[^1].WithoutTrailingTrivia(); - return builder.MoveToImmutable(); - - // If the 'startIndex' argument was passed using named-arguments, return it. - // Otherwise, return null. - IArgumentOperation? GetNamedStartIndexArgumentOrDefault(in RequiredSymbols symbols, IInvocationOperation substringInvocation) + bool TryGetNamedStartIndexArgument(in RequiredSymbols symbols, IInvocationOperation substringInvocation, [NotNullWhen(true)] out IArgumentOperation? namedStartIndexArgument) { RoslynDebug.Assert(symbols.IsAnySubstringMethod(substringInvocation.TargetMethod)); + foreach (var argument in substringInvocation.Arguments) { if (IsNamedArgument(argument) && symbols.IsAnySubstringStartIndexParameter(argument.Parameter)) - return argument; + { + namedStartIndexArgument = argument; + return true; + } } - return null; - } - static IOperation WalkDownImplicitConversion(IOperation operand) - { - if (operand is IConversionOperation { Type: { SpecialType: SpecialType.System_Object or SpecialType.System_String }, IsImplicit: true } conversion) - return conversion.Operand; - return operand; + namedStartIndexArgument = default; + return false; } } - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index 6fe7fc6fd0..e3551c8434 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -37,7 +37,12 @@ public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer private const string AsSpanName = nameof(MemoryExtensions.AsSpan); private const string ConcatName = nameof(string.Concat); - private protected abstract bool IsStringConcatOperation(IBinaryOperation binaryOperation); + /// + /// If the specified binary operation is a string concatenation operation, we try to walk up to the top-most + /// string-concatenation operation that it is part of. If it is not a string-concatenation operation, we simply + /// return false. + /// + private protected abstract bool TryGetTopMostConcatOperation(IBinaryOperation binaryOperation, [NotNullWhen(true)] out IBinaryOperation? rootBinaryOperation); public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); @@ -57,58 +62,49 @@ private void OnCompilationStart(CompilationStartAnalysisContext context) void OnOperationBlockStart(OperationBlockStartAnalysisContext context) { - var rootConcatOperations = PooledConcurrentSet.GetInstance(); + // Maintain set of all top-most concat operations so we don't report sub-expressions of an + // already-reported violation. + // We also don't report any diagnostic if the concat operation has too many operands for the span-based + // Concat overloads to handle. + var topMostConcatOperations = PooledConcurrentSet.GetInstance(); - context.RegisterOperationAction(PopulateRootConcatOperations, OperationKind.Binary); + context.RegisterOperationAction(PopulateTopMostConcatOperations, OperationKind.Binary); context.RegisterOperationBlockEndAction(ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls); - void PopulateRootConcatOperations(OperationAnalysisContext context) + void PopulateTopMostConcatOperations(OperationAnalysisContext context) { + // If the current operation is a string-concatenation operation, walk up to the top-most concat + // operation and add it to the set. var binary = (IBinaryOperation)context.Operation; - if (binary.OperatorKind != symbols.ConcatOperatorKind) + if (!TryGetTopMostConcatOperation(binary, out var topMostConcatOperation)) return; - var topBinaryOperation = WalkUpBinaryOperationChain(binary); - if (!IsStringConcatOperation(topBinaryOperation)) - return; - - rootConcatOperations.Add(topBinaryOperation); + topMostConcatOperations.Add(topMostConcatOperation); } void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context) { - foreach (var root in rootConcatOperations) + // We report diagnostics for all top-most concat operations that contain substring invocations + // when there is an applicable span-based overload of string.Concat + foreach (var operation in topMostConcatOperations) { - var chain = FlattenBinaryOperationChain(root); + var chain = FlattenBinaryOperation(operation); if (chain.Any(IsAnySubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out var _)) { - context.ReportDiagnostic(root.CreateDiagnostic(Rule)); + context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); } } - rootConcatOperations.Free(context.CancellationToken); + topMostConcatOperations.Free(context.CancellationToken); } } bool IsAnySubstringInvocation(IOperation operation) { - return operation.WalkDownConversion() is IInvocationOperation invocation && - (invocation.TargetMethod.Equals(symbols.Substring1, SymbolEqualityComparer.Default) || - invocation.TargetMethod.Equals(symbols.Substring2, SymbolEqualityComparer.Default)); - } - } - - private static IBinaryOperation WalkUpBinaryOperationChain(IBinaryOperation operation) - { - while (operation.Parent is IBinaryOperation parentBinaryOperation && - parentBinaryOperation.OperatorKind == operation.OperatorKind) - { - operation = parentBinaryOperation; + return operation.WalkDownConversion() is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); } - - return operation; } - internal static ImmutableArray FlattenBinaryOperationChain(IBinaryOperation root) + internal static ImmutableArray FlattenBinaryOperation(IBinaryOperation root) { var stack = new Stack(); var builder = ImmutableArray.CreateBuilder(); @@ -155,8 +151,7 @@ internal readonly struct RequiredSymbols private RequiredSymbols( INamedTypeSymbol stringType, INamedTypeSymbol roscharType, IMethodSymbol substring1, IMethodSymbol substring2, - IMethodSymbol asSpan1, IMethodSymbol asSpan2, - BinaryOperatorKind concatOperatorKind) + IMethodSymbol asSpan1, IMethodSymbol asSpan2) { StringType = stringType; RoscharType = roscharType; @@ -164,7 +159,6 @@ private RequiredSymbols( Substring2 = substring2; AsSpan1 = asSpan1; AsSpan2 = asSpan2; - ConcatOperatorKind = concatOperatorKind; RoslynDebug.Assert( StringType is not null && RoscharType is not null && @@ -202,18 +196,10 @@ public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols sy return false; } - var concatOperatorKind = compilation.Language switch - { - LanguageNames.CSharp => BinaryOperatorKind.Add, - LanguageNames.VisualBasic => BinaryOperatorKind.Concatenate, - _ => BinaryOperatorKind.None - }; - symbols = new RequiredSymbols( stringType, roscharType, substring1, substring2, - asSpan1, asSpan2, - concatOperatorKind); + asSpan1, asSpan2); return true; } @@ -223,7 +209,6 @@ public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols sy public IMethodSymbol Substring2 { get; } public IMethodSymbol AsSpan1 { get; } public IMethodSymbol AsSpan2 { get; } - public BinaryOperatorKind ConcatOperatorKind { get; } public IMethodSymbol? GetAsSpanEquivalent(IMethodSymbol? substringMethod) { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index d6dbb80219..8b7c5abe13 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 74a68f8bab..c6e2616a60 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index e2d072c08e..a1c274311a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 20c591156f..1c7133f129 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 477cf058b9..ec7e5df064 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 4b4dbc362d..30567af965 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 8c22daf246..acfe590ed4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 4b9053b0a0..0a42434f03 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index f56187c75f..2b28442296 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 5f75bc3e08..ca1e6f5909 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 40a280c256..4f65b8ef16 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 72fb48bcba..3c9a1bad54 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 3783ef55bb..7439a26415 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index fa5d976400..73fc86d433 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1296,6 +1296,18 @@ Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in |CodeFix|False| --- +## [CA1841](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841): Use span-based string.Concat + +'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2000](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope 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. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 72c37b9580..058d94ecda 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -228,6 +228,25 @@ ] } }, + "CA1841": { + "id": "CA1841", + "shortDescription": "Use span-based string.Concat", + "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", + "defaultLevel": "note", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "CSharpUseSpanBasedStringConcat", + "languages": [ + "C#" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2014": { "id": "CA2014", "shortDescription": "Do not use stackalloc in loops", @@ -5224,6 +5243,25 @@ ] } }, + "CA1841": { + "id": "CA1841", + "shortDescription": "Use span-based string.Concat", + "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", + "defaultLevel": "note", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "BasicUseSpanBasedStringConcat", + "languages": [ + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2016": { "id": "CA2016", "shortDescription": "Forward the 'CancellationToken' parameter to methods", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 75e0f77588..7411bda822 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,3 +2,4 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| +CA1841 | | Use span-based string.Concat | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 0d71a5a214..59eff87744 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -500,11 +500,6 @@ public static IEnumerable Data_NonStringOperands_VB @"String.Concat(foo.AsSpan(1), thing?.ToString())" }; yield return new[] - { - @"maybe & foo.Substring(1)", - @"String.Concat(maybe?.ToString(), foo.AsSpan(1))" - }; - yield return new[] { @"thing & foo.Substring(1, 2) & maybe", @"String.Concat(thing?.ToString(), foo.AsSpan(1, 2), maybe?.ToString())" @@ -552,6 +547,111 @@ public Task NonStringOperands_AreConvertedToString_VB(string testExpression, str }; return test.RunAsync(); } + + [Theory] + [InlineData(@"foo.Substring(1) + (string)explicitTo", @"string.Concat(foo.AsSpan(1), (string)explicitTo)")] + [InlineData(@"(string)explicitTo + foo.Substring(1)", @"string.Concat((string)explicitTo, foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + (string)thing", @"string.Concat(foo.AsSpan(1), (string)thing)")] + [InlineData(@"(string)thing + foo.Substring(1)", @"string.Concat((string)thing, foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + (thing as string)", @"string.Concat(foo.AsSpan(1), thing as string)")] + [InlineData(@"(thing as string) + foo.Substring(1)", @"string.Concat(thing as string, foo.AsSpan(1))")] + public Task ExplicitConversions_ArePreserved_CS(string testExpression, string fixedExpression) + { + string helperTypes = @" +public class ExplicitTo +{ + public static explicit operator string(ExplicitTo operand) => operand?.ToString(); +} + +public class ExplicitFrom +{ + public static explicit operator ExplicitFrom(string operand) => new ExplicitFrom(); +}"; + string format = @" +var explicitTo = new ExplicitTo(); +object thing = bar; +var s = {0};"; + var culture = CultureInfo.InvariantCulture; + string testStatements = string.Format(culture, format, $@"{{|#0:{testExpression}|}}"); + string fixedStatements = string.Format(culture, format, fixedExpression); + + var test = new VerifyCS.Test + { + TestState = { Sources = { CSUsings + CSWithBody(testStatements), helperTypes } }, + FixedState = { Sources = { CSUsings + CSWithBody(fixedStatements), helperTypes } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(@"foo.Substring(1) & CType(explicitTo, String)", @"String.Concat(foo.AsSpan(1), CType(explicitTo, String))")] + [InlineData(@"CType(explicitTo, String) & foo.Substring(1)", @"String.Concat(CType(explicitTo, String), foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) & DirectCast(thing, String)", @"String.Concat(foo.AsSpan(1), DirectCast(thing, String))")] + [InlineData(@"DirectCast(thing, String) & foo.Substring(1)", @"String.Concat(DirectCast(thing, String), foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) & TryCast(thing, String)", @"String.Concat(foo.AsSpan(1), TryCast(thing, String))")] + [InlineData(@"TryCast(thing, String) & foo.Substring(1)", @"String.Concat(TryCast(thing, String), foo.AsSpan(1))")] + public Task ExplicitConversions_ArePreserved_VB(string testExpression, string fixedExpression) + { + string helperTypes = @" +Public Class ExplicitTo + + Public Shared Narrowing Operator CType(operand As ExplicitTo) As String + Return New ExplicitTo() + End Operator +End Class + +Public Class ExplicitFrom + Public Shared Narrowing Operator CType(operand As String) As ExplicitFrom + Return New ExplicitFrom() + End Operator +End Class"; + string format = @" +Dim explicitTo = New ExplicitTo() +Dim thing As Object = bar +Dim s = {0}"; + var culture = CultureInfo.InvariantCulture; + string testStatements = string.Format(culture, format, $@"{{|#0:{testExpression}|}}"); + string fixedStatements = string.Format(culture, format, fixedExpression); + + var test = new VerifyVB.Test + { + TestState = { Sources = { VBUsings + VBWithBody(testStatements), helperTypes } }, + FixedState = { Sources = { VBUsings + VBWithBody(fixedStatements), helperTypes } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + // No C# case because C# has only one concat operator. + [Theory] + [InlineData(@"foo.Substring(1) & bar + baz", @"String.Concat(foo.AsSpan(1), bar, baz)")] + [InlineData(@"foo & bar.Substring(1) + baz", @"String.Concat(foo, bar.AsSpan(1), baz)")] + [InlineData(@"foo & bar + baz.Substring(1)", @"String.Concat(foo, bar, baz.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + bar & baz", @"String.Concat(foo.AsSpan(1), bar, baz)")] + [InlineData(@"foo + bar.Substring(1) & baz", @"String.Concat(foo, bar.AsSpan(1), baz)")] + [InlineData(@"foo + bar & baz.Substring(1)", @"String.Concat(foo, bar, baz.AsSpan(1))")] + [InlineData(@"foo.Substring(1) & bar + baz & baz.Substring(1)", @"String.Concat(foo.AsSpan(1), bar, baz, baz.AsSpan(1))")] + [InlineData(@"foo & bar.Substring(1) + baz & foo", @"String.Concat(foo, bar.AsSpan(1), baz, foo)")] + [InlineData(@"foo.Substring(1) & bar + baz & foo", @"String.Concat(foo.AsSpan(1), bar, baz, foo)")] + [InlineData(@"foo & bar + baz & foo.Substring(1)", @"String.Concat(foo, bar, baz, foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + bar & baz + baz.Substring(1)", @"String.Concat(foo.AsSpan(1), bar, baz, baz.AsSpan(1))")] + [InlineData(@"foo + bar.Substring(1) & baz + foo", @"String.Concat(foo, bar.AsSpan(1), baz, foo)")] + [InlineData(@"foo.Substring(1) + bar & baz + foo", @"String.Concat(foo.AsSpan(1), bar, baz, foo)")] + [InlineData(@"foo + bar & baz + foo.Substring(1)", @"String.Concat(foo, bar, baz, foo.AsSpan(1))")] + public Task MixedAddAndConcatenateOperatorChains_AreReportedAndFixed_VB(string testExpression, string fixedExpression) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody($@"Dim s = {{|#0:{testExpression}|}}"), + FixedCode = VBUsings + VBWithBody($@"Dim s = {fixedExpression}"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } #endregion #region No Diagnostic @@ -637,7 +737,7 @@ public Task NoSubstringInvocations_NoDiagnostic_VB(string expression) [InlineData(@"foo + bar + evil + baz.Substring(1)")] [InlineData(@"foo + evil + bar.Substring(1) + evil")] [InlineData(@"foo + evil + bar.Substring(1) + evil + baz.Substring(1)")] - public Task OverloadedAddOperator_NoDiagnostic(string expression) + public Task OverloadedAddOperator_NoDiagnostic_CS(string expression) { string statements = $@" var evil = new EvilOverloads(); diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb index 7e81120772..d6233591d5 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb @@ -22,12 +22,6 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax) End Function - Private Protected Overrides Function GetOperatorToken(binaryOperation As IBinaryOperation) As SyntaxToken - - Dim syntax = DirectCast(binaryOperation.Syntax, BinaryExpressionSyntax) - Return syntax.OperatorToken - End Function - Private Protected Overrides Function IsSystemNamespaceImported(namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean For Each node As SyntaxNode In namespaceImports @@ -49,16 +43,30 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return False End Function - Private Protected Overrides Function IsNamedArgument(argument As IArgumentOperation) As Boolean - Return DirectCast(argument.Syntax, ArgumentSyntax).IsNamed + Private Protected Overrides Function GenerateConditionalToStringInvocationExpression(expression As SyntaxNode) As SyntaxNode + + Dim identifierName = SyntaxFactory.IdentifierName(ToStringName) + Dim simpleMemberAccess = SyntaxFactory.SimpleMemberAccessExpression(identifierName) + Dim invocation = SyntaxFactory.InvocationExpression(simpleMemberAccess, SyntaxFactory.ArgumentList()) + Return SyntaxFactory.ConditionalAccessExpression(DirectCast(expression, ExpressionSyntax).WithoutTrivia(), invocation).WithTriviaFrom(expression) + End Function + + Private Protected Overrides Function WalkDownBuiltInImplicitConversionOnConcatOperand(operand As IOperation) As IOperation + + Dim conversion = TryCast(operand, IConversionOperation) + If conversion IsNot Nothing AndAlso conversion.IsImplicit AndAlso Not conversion.Conversion.IsUserDefined AndAlso + (conversion.Type.SpecialType = SpecialType.System_String OrElse conversion.Type.SpecialType = SpecialType.System_Object) Then + + Return conversion.Operand + Else + Return operand + End If End Function - Private Protected Overrides Function CreateConditionalToStringInvocation(receiverExpression As SyntaxNode) As SyntaxNode + Private Protected Overrides Function IsNamedArgument(argumentOperation As IArgumentOperation) As Boolean - Dim expression = DirectCast(receiverExpression, ExpressionSyntax) - Dim memberAccessExpression = SyntaxFactory.SimpleMemberAccessExpression(SyntaxFactory.IdentifierName(ToStringName)) - Dim invocationExpression = SyntaxFactory.InvocationExpression(memberAccessExpression) - Return SyntaxFactory.ConditionalAccessExpression(expression.WithoutTrivia(), invocationExpression).WithTriviaFrom(expression) + Dim argumentSyntax = TryCast(argumentOperation.Syntax, ArgumentSyntax) + Return argumentSyntax IsNot Nothing AndAlso argumentSyntax.IsNamed End Function End Class End Namespace diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb index 7fd117bc96..87354fb119 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb @@ -10,8 +10,28 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Public NotInheritable Class BasicUseSpanBasedStringConcat : Inherits UseSpanBasedStringConcat - Private Protected Overrides Function IsStringConcatOperation(binaryOperation As IBinaryOperation) As Boolean - Return binaryOperation.OperatorKind = BinaryOperatorKind.Concatenate + Private Protected Overrides Function TryGetTopMostConcatOperation(binaryOperation As IBinaryOperation, ByRef rootBinaryOperation As IBinaryOperation) As Boolean + + If Not IsStringConcatOperation(binaryOperation) Then + rootBinaryOperation = Nothing + Return False + End If + + Dim parentBinaryOperation = binaryOperation + Dim current As IBinaryOperation + Do + current = parentBinaryOperation + parentBinaryOperation = TryCast(current.Parent, IBinaryOperation) + Loop While parentBinaryOperation IsNot Nothing AndAlso IsStringConcatOperation(parentBinaryOperation) + + rootBinaryOperation = current + Return True + End Function + + Private Shared Function IsStringConcatOperation(operation As IBinaryOperation) As Boolean + + 'OperatorKind will be Concatenate even when the "+" operator is used, provided both operands are strings. + Return operation.OperatorKind = BinaryOperatorKind.Concatenate End Function End Class End Namespace From e98a55b9d20bbc5b543d69657d84b9996fd582e6 Mon Sep 17 00:00:00 2001 From: Newell Clark Date: Tue, 2 Feb 2021 12:13:41 -0500 Subject: [PATCH 026/224] Update src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx Fix title Co-authored-by: Manish Vasani --- .../MicrosoftNetCoreAnalyzersResources.resx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index cf7e21ef06..7ce11c7caf 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1514,9 +1514,9 @@ 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' Use span-based string.Concat - \ No newline at end of file + From 52652c993e457a25efc8aa1844937a34a96150d5 Mon Sep 17 00:00:00 2001 From: Newell Clark Date: Tue, 2 Feb 2021 12:14:02 -0500 Subject: [PATCH 027/224] Update src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx Co-authored-by: Manish Vasani --- .../MicrosoftNetCoreAnalyzersResources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 7ce11c7caf..a3955f94bb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1517,6 +1517,6 @@ Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat + Use span-based 'string.Concat' From ea9a6130f2d1f93ad95d25cba2519e4d8d035f48 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Wed, 3 Feb 2021 21:15:58 -0500 Subject: [PATCH 028/224] - Moved WalkDownImplicitConversionOnConcatOperand to the analyzer, and made it a static method. - Added tests for conditional substring invocation. We report conditional substring invocations, but do not fix them. - Fixed issue where `Imports System` was unnecessarily being added in projects that had `System` as an implicit global import, and added tests for VB projects with and without an implicit global `System` import. - Added null tests for both string and char type symbols, just in case. - Used exact values for expected iteration counts for nested violations tests. - Other minor style changes. --- .../CSharpUseSpanBasedStringConcat.Fixer.cs | 11 +-- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 32 ++++---- .../Runtime/UseSpanBasedStringConcat.cs | 54 ++++++++++--- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 8 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 8 +- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 2 +- .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 4 +- src/NetAnalyzers/RulesMissingDocumentation.md | 2 +- .../Runtime/UseSpanBasedStringConcatTests.cs | 77 +++++++++++++++---- .../BasicUseSpanBasedStringConcat.Fixer.vb | 19 ++--- 21 files changed, 187 insertions(+), 118 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs index 8289df89bf..5c836de2dd 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs @@ -23,7 +23,7 @@ private protected override SyntaxNode ReplaceInvocationMethodName(SyntaxGenerato return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax); } - private protected override bool IsSystemNamespaceImported(IReadOnlyList namespaceImports) + private protected override bool IsSystemNamespaceImported(Project project, IReadOnlyList namespaceImports) { foreach (var import in namespaceImports) { @@ -43,15 +43,6 @@ private protected override SyntaxNode GenerateConditionalToStringInvocationExpre return SyntaxFactory.ConditionalAccessExpression((ExpressionSyntax)expression.WithoutTrivia(), invocation).WithTriviaFrom(expression); } - private protected override IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) - { - if (operand is IConversionOperation conversion && conversion.IsImplicit && conversion.Type.SpecialType == SpecialType.System_Object) - { - return conversion.Operand; - } - return operand; - } - private protected override bool IsNamedArgument(IArgumentOperation argumentOperation) { return argumentOperation.Syntax is ArgumentSyntax argumentSyntax && argumentSyntax.NameColon is not null; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 63405c5fd3..bb21d819c7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,10 +14,8 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Operations; - using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat.RequiredSymbols; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.NetCore.Analyzers.Runtime { @@ -28,18 +27,11 @@ public abstract class UseSpanBasedStringConcatFixer : CodeFixProvider private protected abstract SyntaxNode ReplaceInvocationMethodName(SyntaxGenerator generator, SyntaxNode invocationSyntax, string newName); - private protected abstract bool IsSystemNamespaceImported(IReadOnlyList namespaceImports); + private protected abstract bool IsSystemNamespaceImported(Project project, IReadOnlyList namespaceImports); /// Invoke ToString with the Elvis operator. private protected abstract SyntaxNode GenerateConditionalToStringInvocationExpression(SyntaxNode expression); - /// - /// Remove the built in implicit conversion to object when a non-string operand is concatenated in C#. - /// In Visual Basic, the implicit conversion can be to string or object. - /// User-defined conversions are not removed. - /// - private protected abstract IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand); - private protected abstract bool IsNamedArgument(IArgumentOperation argumentOperation); public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); @@ -65,6 +57,10 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) // Bail out if we don't have a long enough span-based string.Concat overload. if (!symbols.TryGetRoscharConcatMethodWithArity(operands.Length, out IMethodSymbol? roscharConcatMethod)) return; + // Bail if none of the operands are a non-conditional substring invocation. This could be the case if the + // only substring invocations in the expression were conditional invocations. + if (!operands.Any(IsAnyNonConditionalSubstringInvocation)) + return; var codeAction = CodeAction.Create( Resx.UseSpanBasedStringConcatTitle, @@ -72,6 +68,12 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) Resx.UseSpanBasedStringConcatTitle); context.RegisterCodeFix(codeAction, diagnostic); + bool IsAnyNonConditionalSubstringInvocation(IOperation operation) + { + var value = UseSpanBasedStringConcat.WalkDownBuiltInImplicitConversionOnConcatOperand(operation); + return value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); + } + async Task FixConcatOperationChain(CancellationToken cancellationToken) { RoslynDebug.Assert(roscharConcatMethod is not null); @@ -98,12 +100,12 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken .WithLeadingTrivia(leadingTrivia) .WithTrailingTrivia(trailingTrivia); - var newRoot = generator.ReplaceNode(root, concatExpressionSyntax, concatMethodInvocationSyntax); + SyntaxNode newRoot = generator.ReplaceNode(root, concatExpressionSyntax, concatMethodInvocationSyntax); - // Make sure 'System' namespace is imported. - if (!IsSystemNamespaceImported(generator.GetNamespaceImports(newRoot))) + // Import 'System' namespace if it's absent. + if (!IsSystemNamespaceImported(context.Document.Project, generator.GetNamespaceImports(newRoot))) { - var systemNamespaceImport = generator.NamespaceImportDeclaration(nameof(System)); + SyntaxNode systemNamespaceImport = generator.NamespaceImportDeclaration(nameof(System)); newRoot = generator.AddNamespaceImports(newRoot, systemNamespaceImport); } @@ -116,7 +118,7 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken private SyntaxNode ConvertOperandToArgument(in RequiredSymbols symbols, SyntaxGenerator generator, IOperation operand) { - var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operand); + var value = UseSpanBasedStringConcat.WalkDownBuiltInImplicitConversionOnConcatOperand(operand); // Convert substring invocations to equivalent AsSpan invocation. if (value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index e3551c8434..468442b0ef 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; +using Analyzer.Utilities.PooledObjects; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; -using Analyzer.Utilities.PooledObjects; -using System.Collections.Generic; -using System.Linq; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.NetCore.Analyzers.Runtime { @@ -84,12 +84,13 @@ void PopulateTopMostConcatOperations(OperationAnalysisContext context) void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context) { - // We report diagnostics for all top-most concat operations that contain substring invocations + // We report diagnostics for all top-most concat operations that contain + // direct or conditional substring invocations // when there is an applicable span-based overload of string.Concat foreach (var operation in topMostConcatOperations) { var chain = FlattenBinaryOperation(operation); - if (chain.Any(IsAnySubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out var _)) + if (chain.Any(IsAnyDirectOrConditionalSubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out _)) { context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); } @@ -98,9 +99,13 @@ void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAna } } - bool IsAnySubstringInvocation(IOperation operation) + bool IsAnyDirectOrConditionalSubstringInvocation(IOperation operation) { - return operation.WalkDownConversion() is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); + var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operation); + if (value is IConditionalAccessOperation conditionallAccessOperation) + value = conditionallAccessOperation.WhenNotNull; + + return value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); } } @@ -142,6 +147,28 @@ void GoLeft(IBinaryOperation operation) } } + /// + /// Remove the built in implicit conversion on operands to concat. + /// In VB, the conversion can be to either string or object. + /// In C#, the conversion is always to object. + /// + internal static IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) + { + if (operand is not IConversionOperation conversion) + return operand; + if (!conversion.IsImplicit || conversion.Conversion.IsUserDefined) + return conversion; + + switch (conversion.Language) + { + case LanguageNames.CSharp when conversion.Type.SpecialType is SpecialType.System_Object: + case LanguageNames.VisualBasic when conversion.Type.SpecialType is SpecialType.System_Object or SpecialType.System_String: + return conversion.Operand; + default: + return conversion; + } + } + // Use readonly struct instead of record type to save on allocations, since it's not passed by-value. // We aren't comparing these. #pragma warning disable CA1815 // Override equals and operator equals on value types @@ -169,8 +196,15 @@ Substring1 is not null && Substring2 is not null && public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) { var stringType = compilation.GetSpecialType(SpecialType.System_String); - var roscharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1) - ?.Construct(compilation.GetSpecialType(SpecialType.System_Char)); + var charType = compilation.GetSpecialType(SpecialType.System_Char); + + if (stringType is null || charType is null) + { + symbols = default; + return false; + } + + var roscharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1)?.Construct(charType); var memoryExtensionsType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemoryExtensions); if (roscharType is null || memoryExtensionsType is null) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 8b7c5abe13..740564b220 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index c6e2616a60..77ad01ba18 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index a1c274311a..78a7be015a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 1c7133f129..440947f079 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index ec7e5df064..6ec762df7d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 30567af965..6647d41ed0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index acfe590ed4..71d733fc1f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 0a42434f03..bcac6eadee 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 2b28442296..31260e8c1c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index ca1e6f5909..9f0c95bda3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 4f65b8ef16..5bd4e98049 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 3c9a1bad54..ce6bd686b6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 7439a26415..99035d25f7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 73fc86d433..65bdc233ad 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1296,7 +1296,7 @@ Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in |CodeFix|False| --- -## [CA1841](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841): Use span-based string.Concat +## [CA1841](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841): Use span-based 'string.Concat' 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 058d94ecda..65e60a821c 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -230,7 +230,7 @@ }, "CA1841": { "id": "CA1841", - "shortDescription": "Use span-based string.Concat", + "shortDescription": "Use span-based 'string.Concat'", "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", "defaultLevel": "note", "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", @@ -5245,7 +5245,7 @@ }, "CA1841": { "id": "CA1841", - "shortDescription": "Use span-based string.Concat", + "shortDescription": "Use span-based 'string.Concat'", "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", "defaultLevel": "note", "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 7411bda822..2cbae72ee6 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,4 +2,4 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| -CA1841 | | Use span-based string.Concat | +CA1841 | | Use span-based 'string.Concat' | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 59eff87744..48359f3f19 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.VisualBasic; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< @@ -258,7 +259,7 @@ public static IEnumerable Data_NestedViolations_CS @" Consume(string.Concat(Fwd(string.Concat(foo, bar.AsSpan(1))), baz.AsSpan(1)));", new[] { 0, 1 }, - -4 + 2, 2, 2 }; yield return new object[] { @@ -267,23 +268,25 @@ public static IEnumerable Data_NestedViolations_CS @" var _ = string.Concat(Fwd(string.Concat(foo.AsSpan(1), bar.AsSpan(1))), Fwd(string.Concat(foo.AsSpan(1), bar)).AsSpan(1), Fwd(string.Concat(foo, bar.AsSpan(1))));", new[] { 0, 1, 2, 3 }, - -4 + 4, 3, 3 }; } } [Theory] [MemberData(nameof(Data_NestedViolations_CS))] - public Task NestedViolations_AreReportedAndFixed_CS(string testStatements, string fixedStatements, int[] locations, int? iterations = null) + public Task NestedViolations_AreReportedAndFixed_CS( + string testStatements, string fixedStatements, int[] locations, + int? incrementalIterations, int? fixAllInDocumentIterations, int? fixAllIterations) { var test = new VerifyCS.Test { TestCode = CSUsings + CSWithBody(testStatements), FixedCode = CSUsings + CSWithBody(fixedStatements), ReferenceAssemblies = ReferenceAssemblies.Net.Net50, - NumberOfIncrementalIterations = iterations, - NumberOfFixAllInDocumentIterations = iterations, - NumberOfFixAllIterations = iterations + NumberOfIncrementalIterations = incrementalIterations, + NumberOfFixAllInDocumentIterations = fixAllInDocumentIterations, + NumberOfFixAllIterations = fixAllIterations }; test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyCS.Diagnostic(Rule).WithLocation(x))); return test.RunAsync(); @@ -299,7 +302,8 @@ public static IEnumerable Data_NestedViolations_VB Consume({|#0:Fwd({|#1:foo + bar.Substring(1)|}) + baz.Substring(1)|})", @" Consume(String.Concat(Fwd(String.Concat(foo, bar.AsSpan(1))), baz.AsSpan(1)))", - new[] { 0, 1 } + new[] { 0, 1 }, + 2, 2, 2 }; yield return new object[] { @@ -307,30 +311,51 @@ public static IEnumerable Data_NestedViolations_VB Dim s = {|#0:Fwd({|#1:foo.Substring(1) & bar.Substring(1)|}) & Fwd({|#2:foo.Substring(1) & bar|}).Substring(1) & Fwd({|#3:foo & bar.Substring(1)|})|}", @" Dim s = String.Concat(Fwd(String.Concat(foo.AsSpan(1), bar.AsSpan(1))), Fwd(String.Concat(foo.AsSpan(1), bar)).AsSpan(1), Fwd(String.Concat(foo, bar.AsSpan(1))))", - new[] { 0, 1, 2, 3 } + new[] { 0, 1, 2, 3 }, + 4, 3, 3 }; } } [Theory] [MemberData(nameof(Data_NestedViolations_VB))] - public Task NestedViolations_AreReportedAndFixed_VB(string testStatements, string fixedStatements, int[] locations) + public Task NestedViolations_AreReportedAndFixed_VB( + string testStatements, string fixedStatements, int[] locations, + int? incrementalIterations, int? fixAllInDocumentIterations, int? fixAllIterations) { var test = new VerifyVB.Test { TestCode = VBUsings + VBWithBody(testStatements), FixedCode = VBUsings + VBWithBody(fixedStatements), ReferenceAssemblies = ReferenceAssemblies.Net.Net50, - NumberOfIncrementalIterations = -10, - NumberOfFixAllIterations = -10, - NumberOfFixAllInDocumentIterations = -10 + NumberOfIncrementalIterations = incrementalIterations, + NumberOfFixAllIterations = fixAllInDocumentIterations, + NumberOfFixAllInDocumentIterations = fixAllIterations }; test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyVB.Diagnostic(Rule).WithLocation(x))); return test.RunAsync(); } [Fact] - public Task MissingImports_AreAdded_CS() + public Task ConditionalSubstringAccess_IsFlaggedButNotFixed_CS() + { + string statements = @"var s = {|#0:foo?.Substring(1) + bar|};"; + string source = CSUsings + CSWithBody(statements); + + return VerifyCS.VerifyCodeFixAsync(source, VerifyCS.Diagnostic(Rule).WithLocation(0), source); + } + + [Fact] + public Task ConditionalSubstringAccess_IsFlaggedButNotFixed_VB() + { + string statements = @"Dim s = {|#0:foo?.Substring(1) & bar|}"; + string source = VBUsings + VBWithBody(statements); + + return VerifyVB.VerifyCodeFixAsync(source, VerifyVB.Diagnostic(Rule).WithLocation(0), source); + } + + [Fact] + public Task MissingSystemImport_IsAdded_WhenAbsent_CS() { var test = new VerifyCS.Test { @@ -342,8 +367,32 @@ public Task MissingImports_AreAdded_CS() return test.RunAsync(); } + // Visual Basic supports implicit global imports. By default, 'System' is added as a global + // import when you create a project in Visual Studio. + [Fact] + public Task MissingSystemImport_IsNotAdded_WhenIncludedInGlobalImports_VB() + { + var test = new VerifyVB.Test + { + TestCode = VBWithBody(@"Dim s = {|#0:foo & bar.Substring(1)|}"), + FixedCode = VBWithBody(@"Dim s = String.Concat(foo, bar.AsSpan(1))"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + test.SolutionTransforms.Add((s, id) => + { + var project = s.Projects.Single(); + var options = project.CompilationOptions as VisualBasicCompilationOptions; + var globalSystemImport = GlobalImport.Parse(nameof(System)); + options = options.WithGlobalImports(globalSystemImport); + return s.WithProjectCompilationOptions(project.Id, options); + }); + return test.RunAsync(); + } + + // We must add 'Imports System' if it is not included as a global import. [Fact] - public Task MissingImports_AreAdded_VB() + public Task MissingSystemImport_IsAdded_WhenAbsentFromGlobalImports_VB() { var test = new VerifyVB.Test { diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb index d6233591d5..f9fe9cca79 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb @@ -22,7 +22,12 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax) End Function - Private Protected Overrides Function IsSystemNamespaceImported(namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean + Private Protected Overrides Function IsSystemNamespaceImported(project As Project, namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean + + Dim options = DirectCast(project.CompilationOptions, VisualBasicCompilationOptions) + If options.GlobalImports.Any(Function(x) x.Name = NameOf(System)) Then + Return True + End If For Each node As SyntaxNode In namespaceImports Dim importsStatement = TryCast(node, ImportsStatementSyntax) @@ -51,18 +56,6 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return SyntaxFactory.ConditionalAccessExpression(DirectCast(expression, ExpressionSyntax).WithoutTrivia(), invocation).WithTriviaFrom(expression) End Function - Private Protected Overrides Function WalkDownBuiltInImplicitConversionOnConcatOperand(operand As IOperation) As IOperation - - Dim conversion = TryCast(operand, IConversionOperation) - If conversion IsNot Nothing AndAlso conversion.IsImplicit AndAlso Not conversion.Conversion.IsUserDefined AndAlso - (conversion.Type.SpecialType = SpecialType.System_String OrElse conversion.Type.SpecialType = SpecialType.System_Object) Then - - Return conversion.Operand - Else - Return operand - End If - End Function - Private Protected Overrides Function IsNamedArgument(argumentOperation As IArgumentOperation) As Boolean Dim argumentSyntax = TryCast(argumentOperation.Syntax, ArgumentSyntax) From c1baa81b5bf7e9d5b50ae2853ba1c070856c59b6 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Thu, 4 Feb 2021 03:14:26 -0500 Subject: [PATCH 029/224] Fix tests on net472 Some tests didn't explicitly specify net5.0, which caused the tests to fail due to lack of span-based string.Concat. --- .../Runtime/UseSpanBasedStringConcatTests.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 48359f3f19..3207754e83 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -342,7 +342,14 @@ public Task ConditionalSubstringAccess_IsFlaggedButNotFixed_CS() string statements = @"var s = {|#0:foo?.Substring(1) + bar|};"; string source = CSUsings + CSWithBody(statements); - return VerifyCS.VerifyCodeFixAsync(source, VerifyCS.Diagnostic(Rule).WithLocation(0), source); + var test = new VerifyCS.Test + { + TestCode = source, + FixedCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); } [Fact] @@ -351,7 +358,14 @@ public Task ConditionalSubstringAccess_IsFlaggedButNotFixed_VB() string statements = @"Dim s = {|#0:foo?.Substring(1) & bar|}"; string source = VBUsings + VBWithBody(statements); - return VerifyVB.VerifyCodeFixAsync(source, VerifyVB.Diagnostic(Rule).WithLocation(0), source); + var test = new VerifyVB.Test + { + TestCode = source, + FixedCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); } [Fact] From 6d12d20719a4a1eebd8877bff9306112147d3f73 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Thu, 4 Feb 2021 11:50:03 -0500 Subject: [PATCH 030/224] Use Environment.NewLine --- .../Runtime/UseSpanBasedStringConcatTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 3207754e83..97ba044c2c 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -374,7 +374,7 @@ public Task MissingSystemImport_IsAdded_WhenAbsent_CS() var test = new VerifyCS.Test { TestCode = CSWithBody(@"var _ = {|#0:foo + bar.Substring(1)|};"), - FixedCode = $"\r\n{CSUsings}\r\n" + CSWithBody(@"var _ = string.Concat(foo, bar.AsSpan(1));"), + FixedCode = $"{Environment.NewLine}{CSUsings}{Environment.NewLine}" + CSWithBody(@"var _ = string.Concat(foo, bar.AsSpan(1));"), ReferenceAssemblies = ReferenceAssemblies.Net.Net50, ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } }; @@ -411,7 +411,7 @@ public Task MissingSystemImport_IsAdded_WhenAbsentFromGlobalImports_VB() var test = new VerifyVB.Test { TestCode = VBWithBody(@"Dim s = {|#0:foo & bar.Substring(1)|}"), - FixedCode = $"\r\n{VBUsings}\r\n" + VBWithBody(@"Dim s = String.Concat(foo, bar.AsSpan(1))"), + FixedCode = $"{Environment.NewLine}{VBUsings}{Environment.NewLine}" + VBWithBody(@"Dim s = String.Concat(foo, bar.AsSpan(1))"), ReferenceAssemblies = ReferenceAssemblies.Net.Net50, ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } }; @@ -885,7 +885,7 @@ End Sub private static string IndentLines(string body, string indent) { - return indent + body.TrimStart().Replace("\r\n", "\r\n" + indent, StringComparison.Ordinal); + return indent + body.TrimStart().Replace(Environment.NewLine, Environment.NewLine + indent, StringComparison.Ordinal); } #endregion } From 07f50bbe181d8a99db0ecfe509f8958a4bc440a9 Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Fri, 5 Feb 2021 16:57:08 -0800 Subject: [PATCH 031/224] update versions --- eng/Versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index ee50fbe795..6827163fc7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,8 +2,8 @@ 3.3.3 beta1 - 5.0.4 - preview1 + 6.0.0 + preview2 $(VersionPrefix) + From e570e6bd01468248cb8939069756e1de8dc4c7cc Mon Sep 17 00:00:00 2001 From: Briana Eng Date: Thu, 22 Jul 2021 14:08:31 -0700 Subject: [PATCH 181/224] RichNav run worked in main yml -- removing from there and copying it to richnav solo yml --- azure-pipelines-richnav.yml | 45 ++++++++++++++++--------------------- azure-pipelines.yml | 22 +----------------- 2 files changed, 20 insertions(+), 47 deletions(-) diff --git a/azure-pipelines-richnav.yml b/azure-pipelines-richnav.yml index 6f5b29755f..43cb0eee36 100644 --- a/azure-pipelines-richnav.yml +++ b/azure-pipelines-richnav.yml @@ -13,29 +13,22 @@ stages: - stage: build displayName: Build jobs: - - template: /eng/common/templates/jobs/jobs.yml - parameters: - enableRichCodeNavigation: true - richCodeNavigationEnvironment: "production" - richCodeNavigationLanguage: "csharp" - enableMicrobuild: true - enablePublishBuildArtifacts: true - enablePublishTestResults: true - enableTelemetry: true - enableSourceBuild: true - jobs: - - job: Debug_Build - pool: - name: NetCorePublic-Pool - queue: BuildPool.Windows.10.Amd64.VS2019.Pre - variables: - - group: DotNet-Blob-Feed - - group: Publish-Build-Assets - - name: _BuildConfig - value: Debug - steps: - - checkout: self - clean: true - - script: eng\common\CIBuild.cmd - -configuration $(_BuildConfig) - displayName: Build and Test +- template: /eng/common/templates/jobs/jobs.yml + parameters: + enableRichCodeNavigation: true + richCodeNavigationEnvironment: "production" + richCodeNavigationLanguage: "csharp" + jobs: + - job: Debug_Build + pool: + name: NetCorePublic-Pool + queue: BuildPool.Windows.10.Amd64.VS2019.Pre.Open + variables: + - name: _BuildConfig + value: Debug + steps: + - checkout: self + clean: true + - script: eng\common\CIBuild.cmd + -configuration $(_BuildConfig) + displayName: Build and Test diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3466ef52f9..8630354bf5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,27 +12,7 @@ pr: - features/* - 2.9.x -jobs: -- template: /eng/common/templates/jobs/jobs.yml - parameters: - enableRichCodeNavigation: true - richCodeNavigationEnvironment: "production" - richCodeNavigationLanguage: "csharp" - jobs: - - job: Debug_Build - pool: - name: NetCorePublic-Pool - queue: BuildPool.Windows.10.Amd64.VS2019.Pre.Open - variables: - - name: _BuildConfig - value: Debug - steps: - - checkout: self - clean: true - - script: eng\common\CIBuild.cmd - -configuration $(_BuildConfig) - displayName: Build and Test - +jobs: - job: Windows strategy: maxParallel: 4 From c6b009c9fc5155fc2262f57504a7d9567d9cf745 Mon Sep 17 00:00:00 2001 From: Briana Eng Date: Thu, 22 Jul 2021 14:09:09 -0700 Subject: [PATCH 182/224] Remove added whitespace --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8630354bf5..e90ed77c0b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,7 +12,7 @@ pr: - features/* - 2.9.x -jobs: +jobs: - job: Windows strategy: maxParallel: 4 From bee649240feeb4584f7767ff87502b42c31126e8 Mon Sep 17 00:00:00 2001 From: Briana Eng Date: Thu, 22 Jul 2021 14:10:13 -0700 Subject: [PATCH 183/224] Remove unused vars, rename displayName for clarity --- azure-pipelines-richnav.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/azure-pipelines-richnav.yml b/azure-pipelines-richnav.yml index 43cb0eee36..4937cc6d93 100644 --- a/azure-pipelines-richnav.yml +++ b/azure-pipelines-richnav.yml @@ -3,12 +3,6 @@ trigger: - main - release/* -variables: - - name: _TeamName - value: Roslyn - - name: _DotNetArtifactsCategory - value: .NETCore - stages: - stage: build displayName: Build @@ -31,4 +25,4 @@ stages: clean: true - script: eng\common\CIBuild.cmd -configuration $(_BuildConfig) - displayName: Build and Test + displayName: Build and Index From b2648527cd63a490d8e0141ec22bdf51e8c22cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Sat, 19 Jun 2021 21:31:20 +0800 Subject: [PATCH 184/224] =?UTF-8?q?Loc/zh-hans:=20=E8=BF=87=E8=BD=BD=20->?= =?UTF-8?q?=20=E9=87=8D=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf index 3509e1072b..2017eaa843 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf @@ -274,7 +274,7 @@ Add 'System.Uri' overloads - 添加 “System.Uri” 过载 + 添加 “System.Uri” 重载 {Locked="System.Uri"} @@ -1914,4 +1914,4 @@ - \ No newline at end of file + From e539d7d35b2bf09cdb6b8f93ddd9f7b287d12731 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 26 Jul 2021 12:18:54 -0700 Subject: [PATCH 185/224] Install vs-threading analyzers --- .editorconfig | 9 +++++++++ eng/Versions.props | 1 + src/Directory.Build.props | 1 + 3 files changed, 11 insertions(+) diff --git a/.editorconfig b/.editorconfig index 8dc9a28f94..b1bb90fbce 100644 --- a/.editorconfig +++ b/.editorconfig @@ -271,3 +271,12 @@ dotnet_diagnostic.CA1309.severity = suggestion # Analyzers bail-out if the PublicAPI.*.txt file is not found dotnet_public_api_analyzer.require_api_files = true + +### Configuration for vs-threading analyzers executed on this repo ### +[*.{cs,vb}] + +# VSTHRD002: Avoid problematic synchronous waits +dotnet_diagnostic.VSTHRD002.severity = none + +# VSTHRD011: Use AsyncLazy +dotnet_diagnostic.VSTHRD011.severity = none diff --git a/eng/Versions.props b/eng/Versions.props index b0c147944a..5cc78f2372 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -39,6 +39,7 @@ $(DogfoodAnalyzersVersion) $(DogfoodAnalyzersVersion) 2.0.0-pre-20160714 + 17.0.26-alpha 1.0.1-beta1.21202.2 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7921faed16..bae6e2e0e1 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -39,6 +39,7 @@ + From 128c94e7d941f8378aadfcce96795d9721ad4a64 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 26 Jul 2021 12:23:43 -0700 Subject: [PATCH 186/224] Update RelaxTestNamingSuppressor to recognize BenchmarkAttribute --- .../Core/RelaxTestNamingSuppressor.cs | 6 ++++-- .../Compiler/Extensions/IMethodSymbolExtensions.cs | 4 ++-- .../Compiler/Extensions/INamedTypeSymbolExtensions.cs | 7 +++++-- src/Utilities/Compiler/WellKnownTypeNames.cs | 1 + 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/RelaxTestNamingSuppressor.cs b/src/Roslyn.Diagnostics.Analyzers/Core/RelaxTestNamingSuppressor.cs index 01bbd829a4..7b319f0096 100644 --- a/src/Roslyn.Diagnostics.Analyzers/Core/RelaxTestNamingSuppressor.cs +++ b/src/Roslyn.Diagnostics.Analyzers/Core/RelaxTestNamingSuppressor.cs @@ -27,7 +27,9 @@ public sealed class RelaxTestNamingSuppressor : DiagnosticSuppressor public override void ReportSuppressions(SuppressionAnalysisContext context) { - if (context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.XunitFactAttribute) is not { } factAttribute) + context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.XunitFactAttribute, out var factAttribute); + context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.BenchmarkDotNetAttributesBenchmarkAttribute, out var benchmarkAttribute); + if (factAttribute is null && benchmarkAttribute is null) { return; } @@ -48,7 +50,7 @@ public override void ReportSuppressions(SuppressionAnalysisContext context) var semanticModel = context.GetSemanticModel(tree); var declaredSymbol = semanticModel.GetDeclaredSymbol(node, context.CancellationToken); if (declaredSymbol is IMethodSymbol method - && method.IsXUnitTestMethod(knownTestAttributes, factAttribute)) + && method.IsBenchmarkOrXUnitTestMethod(knownTestAttributes, benchmarkAttribute, factAttribute)) { context.ReportSuppression(Suppression.Create(Rule, diagnostic)); } diff --git a/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs b/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs index 320f1f03eb..6e1907cc86 100644 --- a/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs +++ b/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs @@ -686,11 +686,11 @@ public static bool IsArgumentNullCheckMethod(this IMethodSymbol method) !method.Parameters[0].Type.IsValueType; } - public static bool IsXUnitTestMethod(this IMethodSymbol method, ConcurrentDictionary knownTestAttributes, INamedTypeSymbol xunitFactAttribute) + public static bool IsBenchmarkOrXUnitTestMethod(this IMethodSymbol method, ConcurrentDictionary knownTestAttributes, INamedTypeSymbol? benchmarkAttribute, INamedTypeSymbol? xunitFactAttribute) { foreach (var attribute in method.GetAttributes()) { - if (attribute.AttributeClass.IsXUnitTestAttribute(knownTestAttributes, xunitFactAttribute)) + if (attribute.AttributeClass.IsBenchmarkOrXUnitTestAttribute(knownTestAttributes, benchmarkAttribute, xunitFactAttribute)) return true; } diff --git a/src/Utilities/Compiler/Extensions/INamedTypeSymbolExtensions.cs b/src/Utilities/Compiler/Extensions/INamedTypeSymbolExtensions.cs index bae6824961..329f6c58d6 100644 --- a/src/Utilities/Compiler/Extensions/INamedTypeSymbolExtensions.cs +++ b/src/Utilities/Compiler/Extensions/INamedTypeSymbolExtensions.cs @@ -265,12 +265,15 @@ private static bool IsDisqualifyingMember(ISymbol member) return !member.IsStatic && !member.IsDefaultConstructor(); } - public static bool IsXUnitTestAttribute(this INamedTypeSymbol attributeClass, ConcurrentDictionary knownTestAttributes, INamedTypeSymbol xunitFactAttribute) + public static bool IsBenchmarkOrXUnitTestAttribute(this INamedTypeSymbol attributeClass, ConcurrentDictionary knownTestAttributes, INamedTypeSymbol? benchmarkAttribute, INamedTypeSymbol? xunitFactAttribute) { if (knownTestAttributes.TryGetValue(attributeClass, out var isTest)) return isTest; - return knownTestAttributes.GetOrAdd(attributeClass, attributeClass.DerivesFrom(xunitFactAttribute)); + var derivedFromKnown = + (xunitFactAttribute is not null && attributeClass.DerivesFrom(xunitFactAttribute)) + || (benchmarkAttribute is not null && attributeClass.DerivesFrom(benchmarkAttribute)); + return knownTestAttributes.GetOrAdd(attributeClass, derivedFromKnown); } /// diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 74530ab189..c2bd6aca22 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -5,6 +5,7 @@ namespace Analyzer.Utilities // IMPORTANT: Keep this file sorted alphabetically. internal static class WellKnownTypeNames { + public const string BenchmarkDotNetAttributesBenchmarkAttribute = "BenchmarkDotNet.Attributes.BenchmarkAttribute"; public const string MicrosoftAspNetCoreAntiforgeryIAntiforgery = "Microsoft.AspNetCore.Antiforgery.IAntiforgery"; public const string MicrosoftAspNetCoreHttpCookieOptions = "Microsoft.AspNetCore.Http.CookieOptions"; public const string MicrosoftAspNetCoreHttpHttpRequest = "Microsoft.AspNetCore.Http.HttpRequest"; From 54c8c41d4dd36f77cf49e6bde35c06ce4721a3d5 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 26 Jul 2021 12:24:51 -0700 Subject: [PATCH 187/224] Add missing FactAttribute --- .../UnitTests/DeclarePublicApiAnalyzerTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PublicApiAnalyzers/UnitTests/DeclarePublicApiAnalyzerTests.cs b/src/PublicApiAnalyzers/UnitTests/DeclarePublicApiAnalyzerTests.cs index 1607731ff9..bda6cee3bf 100644 --- a/src/PublicApiAnalyzers/UnitTests/DeclarePublicApiAnalyzerTests.cs +++ b/src/PublicApiAnalyzers/UnitTests/DeclarePublicApiAnalyzerTests.cs @@ -1685,6 +1685,7 @@ public C(int value) await VerifyCSharpAdditionalFileFixAsync(source, shippedText, unshippedText, fixedUnshippedText); } + [Fact] [WorkItem(2622, "https://github.com/dotnet/roslyn-analyzers/issues/2622")] public async Task AnalyzerFileMissing_Both_Fix() { From d302777b9255d1c9233aacd7cda4605832e8eaa0 Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Wed, 28 Jul 2021 13:15:05 -0700 Subject: [PATCH 188/224] update headers --- Directory.Build.props | 2 +- Directory.Build.targets | 2 +- eng/AfterSolutionBuild.targets | 2 +- eng/GenerateAnalyzerNuspec.targets | 4 ++-- eng/common/internal/Tools.csproj | 2 +- nuget/Directory.Build.targets | 2 +- .../Microsoft.CodeAnalysis.AnalyzerUtilities.Package.csproj | 2 +- .../Microsoft.CodeAnalysis.Analyzers.Package.csproj | 2 +- .../Microsoft.CodeAnalysis.BannedApiAnalyzers.Package.csproj | 2 +- .../Microsoft.CodeAnalysis.Metrics.Package.csproj | 2 +- .../Microsoft.CodeAnalysis.Metrics.targets | 2 +- ...CodeAnalysis.RulesetToEditorconfigConverter.Package.csproj | 2 +- .../Microsoft.CodeAnalysis.NetAnalyzers.Package.csproj | 2 +- ....CodeAnalysis.PerformanceSensitiveAnalyzers.Package.csproj | 2 +- .../Microsoft.CodeAnalysis.PublicApiAnalyzers.Package.csproj | 2 +- .../Roslyn.Diagnostics.Analyzers.Package.csproj | 2 +- nuget/Text.Analyzers/Text.Analyzers.Package.csproj | 2 +- src/Directory.Build.props | 2 +- src/Directory.Build.targets | 2 +- .../Microsoft.CodeAnalysis.AnalyzerUtilities.csproj | 2 +- .../CSharp/Microsoft.CodeAnalysis.CSharp.Analyzers.csproj | 2 +- .../Core/Microsoft.CodeAnalysis.Analyzers.csproj | 2 +- src/Microsoft.CodeAnalysis.Analyzers/Directory.Build.props | 2 +- .../Setup/Microsoft.CodeAnalysis.Analyzers.Setup.csproj | 2 +- .../Setup/source.extension.vsixmanifest | 2 +- .../Microsoft.CodeAnalysis.Analyzers.UnitTests.csproj | 2 +- .../Microsoft.CodeAnalysis.VisualBasic.Analyzers.vbproj | 2 +- .../Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.csproj | 2 +- .../Core/Microsoft.CodeAnalysis.BannedApiAnalyzers.csproj | 2 +- .../Microsoft.CodeAnalysis.BannedApiAnalyzers.Setup.csproj | 2 +- .../Setup/source.extension.vsixmanifest | 2 +- ...Microsoft.CodeAnalysis.BannedApiAnalyzers.UnitTests.csproj | 2 +- ...crosoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.vbproj | 2 +- .../CSharp/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.csproj | 2 +- .../Core/Microsoft.CodeAnalysis.NetAnalyzers.csproj | 2 +- src/NetAnalyzers/Directory.Build.props | 2 +- src/NetAnalyzers/Directory.Build.targets | 2 +- .../Setup/Microsoft.CodeAnalysis.NetAnalyzers.Setup.csproj | 2 +- src/NetAnalyzers/Setup/source.extension.vsixmanifest | 2 +- .../Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj | 2 +- .../Microsoft.NetFramework.Analyzers.UnitTests.csproj | 2 +- .../Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.vbproj | 2 +- ...t.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.csproj | 2 +- ...ysis.CSharp.PerformanceSensitiveAnalyzers.CodeFixes.csproj | 2 +- ...icrosoft.CodeAnalysis.PerformanceSensitiveAnalyzers.csproj | 2 +- ...ft.CodeAnalysis.PerformanceSensitiveAnalyzers.Setup.csproj | 2 +- .../Setup/source.extension.vsixmanifest | 2 +- ...odeAnalysis.PerformanceSensitiveAnalyzers.UnitTests.csproj | 2 +- .../Microsoft.CodeAnalysis.PublicApiAnalyzers.csproj | 2 +- ...Microsoft.CodeAnalysis.PublicApiAnalyzers.CodeFixes.csproj | 2 +- .../Microsoft.CodeAnalysis.PublicApiAnalyzers.Setup.csproj | 2 +- src/PublicApiAnalyzers/Setup/source.extension.vsixmanifest | 2 +- ...Microsoft.CodeAnalysis.PublicApiAnalyzers.UnitTests.csproj | 2 +- .../CSharp/Roslyn.Diagnostics.CSharp.Analyzers.csproj | 2 +- .../Core/Roslyn.Diagnostics.Analyzers.csproj | 2 +- .../Setup/Roslyn.Diagnostics.Analyzers.Setup.csproj | 2 +- .../Setup/source.extension.vsixmanifest | 2 +- .../UnitTests/Roslyn.Diagnostics.Analyzers.UnitTests.csproj | 2 +- .../Roslyn.Diagnostics.VisualBasic.Analyzers.vbproj | 2 +- src/Test.Utilities/Directory.Build.props | 2 +- src/Test.Utilities/Test.Utilities.csproj | 2 +- src/TestReferenceAssembly/TestReferenceAssembly.csproj | 2 +- src/Text.Analyzers/CSharp/Text.CSharp.Analyzers.csproj | 2 +- src/Text.Analyzers/Core/Text.Analyzers.csproj | 2 +- src/Text.Analyzers/Directory.Build.props | 2 +- src/Text.Analyzers/Setup/Text.Analyzers.Setup.csproj | 2 +- src/Text.Analyzers/Setup/source.extension.vsixmanifest | 2 +- src/Text.Analyzers/UnitTests/Text.Analyzers.UnitTests.csproj | 2 +- .../VisualBasic/Text.VisualBasic.Analyzers.vbproj | 2 +- src/Tools/Metrics/Metrics.Legacy.csproj | 2 +- src/Tools/Metrics/Metrics.csproj | 2 +- .../Tests/RulesetToEditorconfigConverter.UnitTests.csproj | 2 +- src/Utilities.UnitTests/Analyzer.Utilities.UnitTests.csproj | 2 +- .../template/build/Targets/Analyzers.Imports.targets | 2 +- .../template/build/Targets/Analyzers.Settings.targets | 2 +- tools/AnalyzerCodeGenerator/template/src/.nuget/NuGet.Config | 2 +- .../AnalyzerCodeGenerator/template/src/.nuget/packages.config | 2 +- .../src/REPLACE.ME/CSharp/REPLACE.ME.CSharp.Analyzers.csproj | 2 +- .../template/src/REPLACE.ME/Core/REPLACE.ME.Analyzers.csproj | 2 +- .../src/REPLACE.ME/Setup/REPLACE.ME.Analyzers.Setup.csproj | 2 +- .../src/REPLACE.ME/Setup/source.extension.vsixmanifest | 2 +- .../UnitTests/REPLACE.ME.Analyzers.UnitTests.csproj | 2 +- .../VisualBasic/REPLACE.ME.VisualBasic.Analyzers.vbproj | 2 +- .../src/Test/Utilities/DiagnosticsTestUtilities.csproj | 2 +- 84 files changed, 85 insertions(+), 85 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index efc14905e8..2abead734e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,4 @@ - + false diff --git a/Directory.Build.targets b/Directory.Build.targets index 78601706a5..f104db6d1e 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,4 +1,4 @@ - + diff --git a/eng/AfterSolutionBuild.targets b/eng/AfterSolutionBuild.targets index a1ef481722..a59291c9d0 100644 --- a/eng/AfterSolutionBuild.targets +++ b/eng/AfterSolutionBuild.targets @@ -1,4 +1,4 @@ - + diff --git a/eng/GenerateAnalyzerNuspec.targets b/eng/GenerateAnalyzerNuspec.targets index a79e9d33ff..a038f47e21 100644 --- a/eng/GenerateAnalyzerNuspec.targets +++ b/eng/GenerateAnalyzerNuspec.targets @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -19,7 +19,7 @@ false false false - Apache-2.0 + MIT diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj index f46d5efe2e..04b0182859 100644 --- a/eng/common/internal/Tools.csproj +++ b/eng/common/internal/Tools.csproj @@ -1,5 +1,5 @@ - + net472 diff --git a/nuget/Directory.Build.targets b/nuget/Directory.Build.targets index 606960e691..6c3231263f 100644 --- a/nuget/Directory.Build.targets +++ b/nuget/Directory.Build.targets @@ -1,4 +1,4 @@ - + diff --git a/nuget/Microsoft.CodeAnalysis.AnalyzerUtilities/Microsoft.CodeAnalysis.AnalyzerUtilities.Package.csproj b/nuget/Microsoft.CodeAnalysis.AnalyzerUtilities/Microsoft.CodeAnalysis.AnalyzerUtilities.Package.csproj index 918f82205c..951c025035 100644 --- a/nuget/Microsoft.CodeAnalysis.AnalyzerUtilities/Microsoft.CodeAnalysis.AnalyzerUtilities.Package.csproj +++ b/nuget/Microsoft.CodeAnalysis.AnalyzerUtilities/Microsoft.CodeAnalysis.AnalyzerUtilities.Package.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/nuget/Microsoft.CodeAnalysis.Analyzers/Microsoft.CodeAnalysis.Analyzers.Package.csproj b/nuget/Microsoft.CodeAnalysis.Analyzers/Microsoft.CodeAnalysis.Analyzers.Package.csproj index d14f0ad7a0..27bfea4490 100644 --- a/nuget/Microsoft.CodeAnalysis.Analyzers/Microsoft.CodeAnalysis.Analyzers.Package.csproj +++ b/nuget/Microsoft.CodeAnalysis.Analyzers/Microsoft.CodeAnalysis.Analyzers.Package.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/nuget/Microsoft.CodeAnalysis.BannedApiAnalyzers/Microsoft.CodeAnalysis.BannedApiAnalyzers.Package.csproj b/nuget/Microsoft.CodeAnalysis.BannedApiAnalyzers/Microsoft.CodeAnalysis.BannedApiAnalyzers.Package.csproj index 44e0441c3a..9d37433667 100644 --- a/nuget/Microsoft.CodeAnalysis.BannedApiAnalyzers/Microsoft.CodeAnalysis.BannedApiAnalyzers.Package.csproj +++ b/nuget/Microsoft.CodeAnalysis.BannedApiAnalyzers/Microsoft.CodeAnalysis.BannedApiAnalyzers.Package.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/nuget/Microsoft.CodeAnalysis.Metrics/Microsoft.CodeAnalysis.Metrics.Package.csproj b/nuget/Microsoft.CodeAnalysis.Metrics/Microsoft.CodeAnalysis.Metrics.Package.csproj index f93df6647b..beb950b7ab 100644 --- a/nuget/Microsoft.CodeAnalysis.Metrics/Microsoft.CodeAnalysis.Metrics.Package.csproj +++ b/nuget/Microsoft.CodeAnalysis.Metrics/Microsoft.CodeAnalysis.Metrics.Package.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/nuget/Microsoft.CodeAnalysis.Metrics/Microsoft.CodeAnalysis.Metrics.targets b/nuget/Microsoft.CodeAnalysis.Metrics/Microsoft.CodeAnalysis.Metrics.targets index 754918d67a..a1aaa17240 100644 --- a/nuget/Microsoft.CodeAnalysis.Metrics/Microsoft.CodeAnalysis.Metrics.targets +++ b/nuget/Microsoft.CodeAnalysis.Metrics/Microsoft.CodeAnalysis.Metrics.targets @@ -1,4 +1,4 @@ - + $(MSBuildThisFileDirectory)\..\Metrics diff --git a/nuget/Microsoft.CodeAnalysis.RulesetToEditorconfigConverter/Microsoft.CodeAnalysis.RulesetToEditorconfigConverter.Package.csproj b/nuget/Microsoft.CodeAnalysis.RulesetToEditorconfigConverter/Microsoft.CodeAnalysis.RulesetToEditorconfigConverter.Package.csproj index 80a4726088..3b6bdecfcf 100644 --- a/nuget/Microsoft.CodeAnalysis.RulesetToEditorconfigConverter/Microsoft.CodeAnalysis.RulesetToEditorconfigConverter.Package.csproj +++ b/nuget/Microsoft.CodeAnalysis.RulesetToEditorconfigConverter/Microsoft.CodeAnalysis.RulesetToEditorconfigConverter.Package.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/nuget/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.Package.csproj b/nuget/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.Package.csproj index 3314396fdb..fdcf76c10a 100644 --- a/nuget/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.Package.csproj +++ b/nuget/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.Package.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/nuget/PerformanceSensitiveAnalyzers/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.Package.csproj b/nuget/PerformanceSensitiveAnalyzers/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.Package.csproj index 9533da7cd7..795c143cd5 100644 --- a/nuget/PerformanceSensitiveAnalyzers/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.Package.csproj +++ b/nuget/PerformanceSensitiveAnalyzers/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.Package.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/nuget/PublicApiAnalyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.Package.csproj b/nuget/PublicApiAnalyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.Package.csproj index 741235007a..1e81f55d3a 100644 --- a/nuget/PublicApiAnalyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.Package.csproj +++ b/nuget/PublicApiAnalyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.Package.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/nuget/Roslyn.Diagnostics.Analyzers/Roslyn.Diagnostics.Analyzers.Package.csproj b/nuget/Roslyn.Diagnostics.Analyzers/Roslyn.Diagnostics.Analyzers.Package.csproj index 4525797672..1ade54cace 100644 --- a/nuget/Roslyn.Diagnostics.Analyzers/Roslyn.Diagnostics.Analyzers.Package.csproj +++ b/nuget/Roslyn.Diagnostics.Analyzers/Roslyn.Diagnostics.Analyzers.Package.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/nuget/Text.Analyzers/Text.Analyzers.Package.csproj b/nuget/Text.Analyzers/Text.Analyzers.Package.csproj index f8dc0d7020..32fecf491e 100644 --- a/nuget/Text.Analyzers/Text.Analyzers.Package.csproj +++ b/nuget/Text.Analyzers/Text.Analyzers.Package.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bae6e2e0e1..9c88aa908d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,4 +1,4 @@ - + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 46e0c6d77c..c7c28fd9f4 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,4 +1,4 @@ - + + netstandard2.0 diff --git a/src/Microsoft.CodeAnalysis.Analyzers/CSharp/Microsoft.CodeAnalysis.CSharp.Analyzers.csproj b/src/Microsoft.CodeAnalysis.Analyzers/CSharp/Microsoft.CodeAnalysis.CSharp.Analyzers.csproj index 55f4390175..5d4e12d533 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/CSharp/Microsoft.CodeAnalysis.CSharp.Analyzers.csproj +++ b/src/Microsoft.CodeAnalysis.Analyzers/CSharp/Microsoft.CodeAnalysis.CSharp.Analyzers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/Microsoft.CodeAnalysis.Analyzers.csproj b/src/Microsoft.CodeAnalysis.Analyzers/Core/Microsoft.CodeAnalysis.Analyzers.csproj index fd3ef65f58..2568c9ae68 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/Microsoft.CodeAnalysis.Analyzers.csproj +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/Microsoft.CodeAnalysis.Analyzers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Directory.Build.props b/src/Microsoft.CodeAnalysis.Analyzers/Directory.Build.props index 4bdb092940..a2319f288c 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Directory.Build.props +++ b/src/Microsoft.CodeAnalysis.Analyzers/Directory.Build.props @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Setup/Microsoft.CodeAnalysis.Analyzers.Setup.csproj b/src/Microsoft.CodeAnalysis.Analyzers/Setup/Microsoft.CodeAnalysis.Analyzers.Setup.csproj index 662962a3e6..6fb0e95994 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Setup/Microsoft.CodeAnalysis.Analyzers.Setup.csproj +++ b/src/Microsoft.CodeAnalysis.Analyzers/Setup/Microsoft.CodeAnalysis.Analyzers.Setup.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Setup/source.extension.vsixmanifest b/src/Microsoft.CodeAnalysis.Analyzers/Setup/source.extension.vsixmanifest index 574531a6f4..573309b407 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Setup/source.extension.vsixmanifest +++ b/src/Microsoft.CodeAnalysis.Analyzers/Setup/source.extension.vsixmanifest @@ -1,5 +1,5 @@  - + diff --git a/src/Microsoft.CodeAnalysis.Analyzers/UnitTests/Microsoft.CodeAnalysis.Analyzers.UnitTests.csproj b/src/Microsoft.CodeAnalysis.Analyzers/UnitTests/Microsoft.CodeAnalysis.Analyzers.UnitTests.csproj index 77910b02d3..8a00683584 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/UnitTests/Microsoft.CodeAnalysis.Analyzers.UnitTests.csproj +++ b/src/Microsoft.CodeAnalysis.Analyzers/UnitTests/Microsoft.CodeAnalysis.Analyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/Microsoft.CodeAnalysis.Analyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.Analyzers.vbproj b/src/Microsoft.CodeAnalysis.Analyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.Analyzers.vbproj index 289f8fcea5..2faafc428d 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.Analyzers.vbproj +++ b/src/Microsoft.CodeAnalysis.Analyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.Analyzers.vbproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/CSharp/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.csproj b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/CSharp/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.csproj index b629875db8..927db9e576 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/CSharp/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.csproj +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/CSharp/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Core/Microsoft.CodeAnalysis.BannedApiAnalyzers.csproj b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Core/Microsoft.CodeAnalysis.BannedApiAnalyzers.csproj index 1f89c1cec9..cc036a5b2f 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Core/Microsoft.CodeAnalysis.BannedApiAnalyzers.csproj +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Core/Microsoft.CodeAnalysis.BannedApiAnalyzers.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Setup/Microsoft.CodeAnalysis.BannedApiAnalyzers.Setup.csproj b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Setup/Microsoft.CodeAnalysis.BannedApiAnalyzers.Setup.csproj index 28cb284541..dfd37101ee 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Setup/Microsoft.CodeAnalysis.BannedApiAnalyzers.Setup.csproj +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Setup/Microsoft.CodeAnalysis.BannedApiAnalyzers.Setup.csproj @@ -1,4 +1,4 @@ - + net46 diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Setup/source.extension.vsixmanifest b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Setup/source.extension.vsixmanifest index eb39bd968a..ccdd43156b 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Setup/source.extension.vsixmanifest +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Setup/source.extension.vsixmanifest @@ -1,5 +1,5 @@ - + diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/Microsoft.CodeAnalysis.BannedApiAnalyzers.UnitTests.csproj b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/Microsoft.CodeAnalysis.BannedApiAnalyzers.UnitTests.csproj index 00d254c02a..c895762b1f 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/Microsoft.CodeAnalysis.BannedApiAnalyzers.UnitTests.csproj +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/Microsoft.CodeAnalysis.BannedApiAnalyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.vbproj b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.vbproj index 0958cc7ed9..052296b717 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.vbproj +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.vbproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/src/NetAnalyzers/CSharp/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.csproj b/src/NetAnalyzers/CSharp/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.csproj index 829dde4fa9..d4d30cac22 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.csproj +++ b/src/NetAnalyzers/CSharp/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/NetAnalyzers/Core/Microsoft.CodeAnalysis.NetAnalyzers.csproj b/src/NetAnalyzers/Core/Microsoft.CodeAnalysis.NetAnalyzers.csproj index c1c2393500..e76e8605dc 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeAnalysis.NetAnalyzers.csproj +++ b/src/NetAnalyzers/Core/Microsoft.CodeAnalysis.NetAnalyzers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/NetAnalyzers/Directory.Build.props b/src/NetAnalyzers/Directory.Build.props index 2f2081471a..5187965507 100644 --- a/src/NetAnalyzers/Directory.Build.props +++ b/src/NetAnalyzers/Directory.Build.props @@ -1,4 +1,4 @@ - + diff --git a/src/NetAnalyzers/Directory.Build.targets b/src/NetAnalyzers/Directory.Build.targets index ccc3bfe132..ebc0507b1f 100644 --- a/src/NetAnalyzers/Directory.Build.targets +++ b/src/NetAnalyzers/Directory.Build.targets @@ -1,4 +1,4 @@ - + diff --git a/src/NetAnalyzers/Setup/Microsoft.CodeAnalysis.NetAnalyzers.Setup.csproj b/src/NetAnalyzers/Setup/Microsoft.CodeAnalysis.NetAnalyzers.Setup.csproj index 585797c8a0..7c60bc0b5a 100644 --- a/src/NetAnalyzers/Setup/Microsoft.CodeAnalysis.NetAnalyzers.Setup.csproj +++ b/src/NetAnalyzers/Setup/Microsoft.CodeAnalysis.NetAnalyzers.Setup.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/src/NetAnalyzers/Setup/source.extension.vsixmanifest b/src/NetAnalyzers/Setup/source.extension.vsixmanifest index c60db5fa48..9f770c83ac 100644 --- a/src/NetAnalyzers/Setup/source.extension.vsixmanifest +++ b/src/NetAnalyzers/Setup/source.extension.vsixmanifest @@ -1,5 +1,5 @@ - + diff --git a/src/NetAnalyzers/UnitTests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj b/src/NetAnalyzers/UnitTests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj index 9feef642b0..058b1699e5 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj +++ b/src/NetAnalyzers/UnitTests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetFramework.Analyzers/Microsoft.NetFramework.Analyzers.UnitTests.csproj b/src/NetAnalyzers/UnitTests/Microsoft.NetFramework.Analyzers/Microsoft.NetFramework.Analyzers.UnitTests.csproj index ee7da9cbec..58fe63bb4d 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetFramework.Analyzers/Microsoft.NetFramework.Analyzers.UnitTests.csproj +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetFramework.Analyzers/Microsoft.NetFramework.Analyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.vbproj b/src/NetAnalyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.vbproj index 4a694397f9..fd41206976 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.vbproj +++ b/src/NetAnalyzers/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.vbproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/PerformanceSensitiveAnalyzers/CSharp/Analyzers/Microsoft.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.csproj b/src/PerformanceSensitiveAnalyzers/CSharp/Analyzers/Microsoft.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.csproj index deb34836cd..1f4c11366f 100644 --- a/src/PerformanceSensitiveAnalyzers/CSharp/Analyzers/Microsoft.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.csproj +++ b/src/PerformanceSensitiveAnalyzers/CSharp/Analyzers/Microsoft.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/src/PerformanceSensitiveAnalyzers/CSharp/CodeFixes/Microsoft.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.CodeFixes.csproj b/src/PerformanceSensitiveAnalyzers/CSharp/CodeFixes/Microsoft.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.CodeFixes.csproj index d7111e53f1..eb919ca64a 100644 --- a/src/PerformanceSensitiveAnalyzers/CSharp/CodeFixes/Microsoft.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.CodeFixes.csproj +++ b/src/PerformanceSensitiveAnalyzers/CSharp/CodeFixes/Microsoft.CodeAnalysis.CSharp.PerformanceSensitiveAnalyzers.CodeFixes.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/src/PerformanceSensitiveAnalyzers/Core/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.csproj b/src/PerformanceSensitiveAnalyzers/Core/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.csproj index 0149999420..313289e03d 100644 --- a/src/PerformanceSensitiveAnalyzers/Core/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.csproj +++ b/src/PerformanceSensitiveAnalyzers/Core/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/src/PerformanceSensitiveAnalyzers/Setup/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.Setup.csproj b/src/PerformanceSensitiveAnalyzers/Setup/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.Setup.csproj index ab006f9056..38f9fb7f0a 100644 --- a/src/PerformanceSensitiveAnalyzers/Setup/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.Setup.csproj +++ b/src/PerformanceSensitiveAnalyzers/Setup/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.Setup.csproj @@ -1,4 +1,4 @@ - + net46 diff --git a/src/PerformanceSensitiveAnalyzers/Setup/source.extension.vsixmanifest b/src/PerformanceSensitiveAnalyzers/Setup/source.extension.vsixmanifest index fca2f698c3..4467a0b3bd 100644 --- a/src/PerformanceSensitiveAnalyzers/Setup/source.extension.vsixmanifest +++ b/src/PerformanceSensitiveAnalyzers/Setup/source.extension.vsixmanifest @@ -1,5 +1,5 @@ - + diff --git a/src/PerformanceSensitiveAnalyzers/UnitTests/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.UnitTests.csproj b/src/PerformanceSensitiveAnalyzers/UnitTests/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.UnitTests.csproj index a405d19833..660e704e55 100644 --- a/src/PerformanceSensitiveAnalyzers/UnitTests/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.UnitTests.csproj +++ b/src/PerformanceSensitiveAnalyzers/UnitTests/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/PublicApiAnalyzers/Core/Analyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.csproj b/src/PublicApiAnalyzers/Core/Analyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.csproj index 1b976eef82..d378b47bdd 100644 --- a/src/PublicApiAnalyzers/Core/Analyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.csproj +++ b/src/PublicApiAnalyzers/Core/Analyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/src/PublicApiAnalyzers/Core/CodeFixes/Microsoft.CodeAnalysis.PublicApiAnalyzers.CodeFixes.csproj b/src/PublicApiAnalyzers/Core/CodeFixes/Microsoft.CodeAnalysis.PublicApiAnalyzers.CodeFixes.csproj index 62cbf0b503..aba15d7ac8 100644 --- a/src/PublicApiAnalyzers/Core/CodeFixes/Microsoft.CodeAnalysis.PublicApiAnalyzers.CodeFixes.csproj +++ b/src/PublicApiAnalyzers/Core/CodeFixes/Microsoft.CodeAnalysis.PublicApiAnalyzers.CodeFixes.csproj @@ -1,4 +1,4 @@ - + netstandard1.3 diff --git a/src/PublicApiAnalyzers/Setup/Microsoft.CodeAnalysis.PublicApiAnalyzers.Setup.csproj b/src/PublicApiAnalyzers/Setup/Microsoft.CodeAnalysis.PublicApiAnalyzers.Setup.csproj index dcda3c2d9b..f9034e4fdc 100644 --- a/src/PublicApiAnalyzers/Setup/Microsoft.CodeAnalysis.PublicApiAnalyzers.Setup.csproj +++ b/src/PublicApiAnalyzers/Setup/Microsoft.CodeAnalysis.PublicApiAnalyzers.Setup.csproj @@ -1,4 +1,4 @@ - + net46 diff --git a/src/PublicApiAnalyzers/Setup/source.extension.vsixmanifest b/src/PublicApiAnalyzers/Setup/source.extension.vsixmanifest index 97fcc78bc5..0c0a58aae1 100644 --- a/src/PublicApiAnalyzers/Setup/source.extension.vsixmanifest +++ b/src/PublicApiAnalyzers/Setup/source.extension.vsixmanifest @@ -1,5 +1,5 @@ - + diff --git a/src/PublicApiAnalyzers/UnitTests/Microsoft.CodeAnalysis.PublicApiAnalyzers.UnitTests.csproj b/src/PublicApiAnalyzers/UnitTests/Microsoft.CodeAnalysis.PublicApiAnalyzers.UnitTests.csproj index 63cf6a3397..2d213aa4db 100644 --- a/src/PublicApiAnalyzers/UnitTests/Microsoft.CodeAnalysis.PublicApiAnalyzers.UnitTests.csproj +++ b/src/PublicApiAnalyzers/UnitTests/Microsoft.CodeAnalysis.PublicApiAnalyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/Roslyn.Diagnostics.Analyzers/CSharp/Roslyn.Diagnostics.CSharp.Analyzers.csproj b/src/Roslyn.Diagnostics.Analyzers/CSharp/Roslyn.Diagnostics.CSharp.Analyzers.csproj index b615dce017..e62266894f 100644 --- a/src/Roslyn.Diagnostics.Analyzers/CSharp/Roslyn.Diagnostics.CSharp.Analyzers.csproj +++ b/src/Roslyn.Diagnostics.Analyzers/CSharp/Roslyn.Diagnostics.CSharp.Analyzers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/Roslyn.Diagnostics.Analyzers.csproj b/src/Roslyn.Diagnostics.Analyzers/Core/Roslyn.Diagnostics.Analyzers.csproj index 9298299c73..60450aee0e 100644 --- a/src/Roslyn.Diagnostics.Analyzers/Core/Roslyn.Diagnostics.Analyzers.csproj +++ b/src/Roslyn.Diagnostics.Analyzers/Core/Roslyn.Diagnostics.Analyzers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Roslyn.Diagnostics.Analyzers/Setup/Roslyn.Diagnostics.Analyzers.Setup.csproj b/src/Roslyn.Diagnostics.Analyzers/Setup/Roslyn.Diagnostics.Analyzers.Setup.csproj index 0c836b8c84..cf058c09a4 100644 --- a/src/Roslyn.Diagnostics.Analyzers/Setup/Roslyn.Diagnostics.Analyzers.Setup.csproj +++ b/src/Roslyn.Diagnostics.Analyzers/Setup/Roslyn.Diagnostics.Analyzers.Setup.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/src/Roslyn.Diagnostics.Analyzers/Setup/source.extension.vsixmanifest b/src/Roslyn.Diagnostics.Analyzers/Setup/source.extension.vsixmanifest index 28eddb674c..d3443b8c7b 100644 --- a/src/Roslyn.Diagnostics.Analyzers/Setup/source.extension.vsixmanifest +++ b/src/Roslyn.Diagnostics.Analyzers/Setup/source.extension.vsixmanifest @@ -1,5 +1,5 @@ - + diff --git a/src/Roslyn.Diagnostics.Analyzers/UnitTests/Roslyn.Diagnostics.Analyzers.UnitTests.csproj b/src/Roslyn.Diagnostics.Analyzers/UnitTests/Roslyn.Diagnostics.Analyzers.UnitTests.csproj index dbd586f4fa..38ea8f8320 100644 --- a/src/Roslyn.Diagnostics.Analyzers/UnitTests/Roslyn.Diagnostics.Analyzers.UnitTests.csproj +++ b/src/Roslyn.Diagnostics.Analyzers/UnitTests/Roslyn.Diagnostics.Analyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/Roslyn.Diagnostics.Analyzers/VisualBasic/Roslyn.Diagnostics.VisualBasic.Analyzers.vbproj b/src/Roslyn.Diagnostics.Analyzers/VisualBasic/Roslyn.Diagnostics.VisualBasic.Analyzers.vbproj index 82a6948ef6..509a3c0698 100644 --- a/src/Roslyn.Diagnostics.Analyzers/VisualBasic/Roslyn.Diagnostics.VisualBasic.Analyzers.vbproj +++ b/src/Roslyn.Diagnostics.Analyzers/VisualBasic/Roslyn.Diagnostics.VisualBasic.Analyzers.vbproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Test.Utilities/Directory.Build.props b/src/Test.Utilities/Directory.Build.props index 222262c696..11ae4fb7e6 100644 --- a/src/Test.Utilities/Directory.Build.props +++ b/src/Test.Utilities/Directory.Build.props @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) diff --git a/src/Test.Utilities/Test.Utilities.csproj b/src/Test.Utilities/Test.Utilities.csproj index b21e1815f4..2ed6333894 100644 --- a/src/Test.Utilities/Test.Utilities.csproj +++ b/src/Test.Utilities/Test.Utilities.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/TestReferenceAssembly/TestReferenceAssembly.csproj b/src/TestReferenceAssembly/TestReferenceAssembly.csproj index 192e2b6b06..b1d18e913a 100644 --- a/src/TestReferenceAssembly/TestReferenceAssembly.csproj +++ b/src/TestReferenceAssembly/TestReferenceAssembly.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net46 diff --git a/src/Text.Analyzers/CSharp/Text.CSharp.Analyzers.csproj b/src/Text.Analyzers/CSharp/Text.CSharp.Analyzers.csproj index 937c2c79c6..e19a429acc 100644 --- a/src/Text.Analyzers/CSharp/Text.CSharp.Analyzers.csproj +++ b/src/Text.Analyzers/CSharp/Text.CSharp.Analyzers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Text.Analyzers/Core/Text.Analyzers.csproj b/src/Text.Analyzers/Core/Text.Analyzers.csproj index 49f695b5c7..fbf8c56d23 100644 --- a/src/Text.Analyzers/Core/Text.Analyzers.csproj +++ b/src/Text.Analyzers/Core/Text.Analyzers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Text.Analyzers/Directory.Build.props b/src/Text.Analyzers/Directory.Build.props index 16412d7f06..a3aa282953 100644 --- a/src/Text.Analyzers/Directory.Build.props +++ b/src/Text.Analyzers/Directory.Build.props @@ -1,4 +1,4 @@ - + diff --git a/src/Text.Analyzers/Setup/Text.Analyzers.Setup.csproj b/src/Text.Analyzers/Setup/Text.Analyzers.Setup.csproj index 010fafc196..fe1507e269 100644 --- a/src/Text.Analyzers/Setup/Text.Analyzers.Setup.csproj +++ b/src/Text.Analyzers/Setup/Text.Analyzers.Setup.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/src/Text.Analyzers/Setup/source.extension.vsixmanifest b/src/Text.Analyzers/Setup/source.extension.vsixmanifest index 89f225defe..59fa885556 100644 --- a/src/Text.Analyzers/Setup/source.extension.vsixmanifest +++ b/src/Text.Analyzers/Setup/source.extension.vsixmanifest @@ -1,5 +1,5 @@ - + diff --git a/src/Text.Analyzers/UnitTests/Text.Analyzers.UnitTests.csproj b/src/Text.Analyzers/UnitTests/Text.Analyzers.UnitTests.csproj index 57e4780574..1d3a690632 100644 --- a/src/Text.Analyzers/UnitTests/Text.Analyzers.UnitTests.csproj +++ b/src/Text.Analyzers/UnitTests/Text.Analyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/Text.Analyzers/VisualBasic/Text.VisualBasic.Analyzers.vbproj b/src/Text.Analyzers/VisualBasic/Text.VisualBasic.Analyzers.vbproj index 2fcb5cd9a4..534865e81d 100644 --- a/src/Text.Analyzers/VisualBasic/Text.VisualBasic.Analyzers.vbproj +++ b/src/Text.Analyzers/VisualBasic/Text.VisualBasic.Analyzers.vbproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Tools/Metrics/Metrics.Legacy.csproj b/src/Tools/Metrics/Metrics.Legacy.csproj index afa796a42a..b9f8414737 100644 --- a/src/Tools/Metrics/Metrics.Legacy.csproj +++ b/src/Tools/Metrics/Metrics.Legacy.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/src/Tools/Metrics/Metrics.csproj b/src/Tools/Metrics/Metrics.csproj index be92726130..02e3462df8 100644 --- a/src/Tools/Metrics/Metrics.csproj +++ b/src/Tools/Metrics/Metrics.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/src/Tools/RulesetToEditorconfigConverter/Tests/RulesetToEditorconfigConverter.UnitTests.csproj b/src/Tools/RulesetToEditorconfigConverter/Tests/RulesetToEditorconfigConverter.UnitTests.csproj index 07195f5a61..027ca2b09a 100644 --- a/src/Tools/RulesetToEditorconfigConverter/Tests/RulesetToEditorconfigConverter.UnitTests.csproj +++ b/src/Tools/RulesetToEditorconfigConverter/Tests/RulesetToEditorconfigConverter.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/src/Utilities.UnitTests/Analyzer.Utilities.UnitTests.csproj b/src/Utilities.UnitTests/Analyzer.Utilities.UnitTests.csproj index badfbe3641..7aa61f9920 100644 --- a/src/Utilities.UnitTests/Analyzer.Utilities.UnitTests.csproj +++ b/src/Utilities.UnitTests/Analyzer.Utilities.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net472 diff --git a/tools/AnalyzerCodeGenerator/template/build/Targets/Analyzers.Imports.targets b/tools/AnalyzerCodeGenerator/template/build/Targets/Analyzers.Imports.targets index b21f336525..7dde1fafd0 100644 --- a/tools/AnalyzerCodeGenerator/template/build/Targets/Analyzers.Imports.targets +++ b/tools/AnalyzerCodeGenerator/template/build/Targets/Analyzers.Imports.targets @@ -1,5 +1,5 @@ - + diff --git a/tools/AnalyzerCodeGenerator/template/build/Targets/Analyzers.Settings.targets b/tools/AnalyzerCodeGenerator/template/build/Targets/Analyzers.Settings.targets index 675119cad7..f54ac5672f 100644 --- a/tools/AnalyzerCodeGenerator/template/build/Targets/Analyzers.Settings.targets +++ b/tools/AnalyzerCodeGenerator/template/build/Targets/Analyzers.Settings.targets @@ -1,5 +1,5 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) diff --git a/tools/AnalyzerCodeGenerator/template/src/.nuget/NuGet.Config b/tools/AnalyzerCodeGenerator/template/src/.nuget/NuGet.Config index 61ac4fb96d..7bea24edfe 100644 --- a/tools/AnalyzerCodeGenerator/template/src/.nuget/NuGet.Config +++ b/tools/AnalyzerCodeGenerator/template/src/.nuget/NuGet.Config @@ -1,5 +1,5 @@  - + diff --git a/tools/AnalyzerCodeGenerator/template/src/.nuget/packages.config b/tools/AnalyzerCodeGenerator/template/src/.nuget/packages.config index 900c2f6cbe..eb6d6b36ec 100644 --- a/tools/AnalyzerCodeGenerator/template/src/.nuget/packages.config +++ b/tools/AnalyzerCodeGenerator/template/src/.nuget/packages.config @@ -1,5 +1,5 @@  - + diff --git a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/CSharp/REPLACE.ME.CSharp.Analyzers.csproj b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/CSharp/REPLACE.ME.CSharp.Analyzers.csproj index 55b34b861f..16fd9e583c 100644 --- a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/CSharp/REPLACE.ME.CSharp.Analyzers.csproj +++ b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/CSharp/REPLACE.ME.CSharp.Analyzers.csproj @@ -1,5 +1,5 @@  - + diff --git a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Core/REPLACE.ME.Analyzers.csproj b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Core/REPLACE.ME.Analyzers.csproj index 7e7dc5490f..fdd29f4221 100644 --- a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Core/REPLACE.ME.Analyzers.csproj +++ b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Core/REPLACE.ME.Analyzers.csproj @@ -1,5 +1,5 @@  - + diff --git a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Setup/REPLACE.ME.Analyzers.Setup.csproj b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Setup/REPLACE.ME.Analyzers.Setup.csproj index 078e414e62..31fad3f702 100644 --- a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Setup/REPLACE.ME.Analyzers.Setup.csproj +++ b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Setup/REPLACE.ME.Analyzers.Setup.csproj @@ -1,4 +1,4 @@ - + diff --git a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Setup/source.extension.vsixmanifest b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Setup/source.extension.vsixmanifest index bae20ee8b2..e20cfb62fe 100644 --- a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Setup/source.extension.vsixmanifest +++ b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/Setup/source.extension.vsixmanifest @@ -1,5 +1,5 @@  - + diff --git a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/UnitTests/REPLACE.ME.Analyzers.UnitTests.csproj b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/UnitTests/REPLACE.ME.Analyzers.UnitTests.csproj index 8132384d6f..d02cbb8d40 100644 --- a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/UnitTests/REPLACE.ME.Analyzers.UnitTests.csproj +++ b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/UnitTests/REPLACE.ME.Analyzers.UnitTests.csproj @@ -1,5 +1,5 @@  - + diff --git a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/VisualBasic/REPLACE.ME.VisualBasic.Analyzers.vbproj b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/VisualBasic/REPLACE.ME.VisualBasic.Analyzers.vbproj index b23ba1d553..559edfc9b2 100644 --- a/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/VisualBasic/REPLACE.ME.VisualBasic.Analyzers.vbproj +++ b/tools/AnalyzerCodeGenerator/template/src/REPLACE.ME/VisualBasic/REPLACE.ME.VisualBasic.Analyzers.vbproj @@ -1,5 +1,5 @@  - + diff --git a/tools/AnalyzerCodeGenerator/template/src/Test/Utilities/DiagnosticsTestUtilities.csproj b/tools/AnalyzerCodeGenerator/template/src/Test/Utilities/DiagnosticsTestUtilities.csproj index 7313689b5e..1068f26441 100644 --- a/tools/AnalyzerCodeGenerator/template/src/Test/Utilities/DiagnosticsTestUtilities.csproj +++ b/tools/AnalyzerCodeGenerator/template/src/Test/Utilities/DiagnosticsTestUtilities.csproj @@ -1,5 +1,5 @@  - + From 66cb185795307de467319bd9ac2e222cb1f18ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Thu, 29 Jul 2021 10:46:43 +0200 Subject: [PATCH 189/224] Refactor tests to use non-obselete location API --- .../MarkMembersAsStaticTests.cs | 147 ++++++++---------- 1 file changed, 66 insertions(+), 81 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/QualityGuidelines/MarkMembersAsStaticTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/QualityGuidelines/MarkMembersAsStaticTests.cs index 1dc4e16ba0..7863279565 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/QualityGuidelines/MarkMembersAsStaticTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/QualityGuidelines/MarkMembersAsStaticTests.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Testing; using Test.Utilities; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< @@ -26,49 +25,49 @@ public class MembersTests internal static int s_field; public const int Zero = 0; - public int Method1(string name) + public int {|#0:Method1|}(string name) { return name.Length; } - public void Method2() { } + public void {|#1:Method2|}() { } - public void Method3() + public void {|#2:Method3|}() { s_field = 4; } - public int Method4() + public int {|#3:Method4|}() { return Zero; } - public int Property + public int {|#4:Property|} { get { return 5; } } - public int Property2 + public int {|#5:Property2|} { set { s_field = value; } } - public int MyProperty + public int {|#6:MyProperty|} { get { return 10; } set { System.Console.WriteLine(value); } } - public event System.EventHandler CustomEvent { add {} remove {} } + public event System.EventHandler {|#7:CustomEvent|} { add {} remove {} } }", - GetCSharpResultAt(7, 16, "Method1"), - GetCSharpResultAt(12, 17, "Method2"), - GetCSharpResultAt(14, 17, "Method3"), - GetCSharpResultAt(19, 16, "Method4"), - GetCSharpResultAt(24, 16, "Property"), - GetCSharpResultAt(29, 16, "Property2"), - GetCSharpResultAt(34, 16, "MyProperty"), - GetCSharpResultAt(40, 56, "CustomEvent")); + VerifyCS.Diagnostic().WithLocation(0).WithArguments("Method1"), + VerifyCS.Diagnostic().WithLocation(1).WithArguments("Method2"), + VerifyCS.Diagnostic().WithLocation(2).WithArguments("Method3"), + VerifyCS.Diagnostic().WithLocation(3).WithArguments("Method4"), + VerifyCS.Diagnostic().WithLocation(4).WithArguments("Property"), + VerifyCS.Diagnostic().WithLocation(5).WithArguments("Property2"), + VerifyCS.Diagnostic().WithLocation(6).WithArguments("MyProperty"), + VerifyCS.Diagnostic().WithLocation(7).WithArguments("CustomEvent")); } [Fact] @@ -81,34 +80,34 @@ Public Class MembersTests Shared s_field As Integer Public Const Zero As Integer = 0 - Public Function Method1(name As String) As Integer + Public Function {|#0:Method1|}(name As String) As Integer Return name.Length End Function - Public Sub Method2() + Public Sub {|#1:Method2|}() End Sub - Public Sub Method3() + Public Sub {|#2:Method3|}() s_field = 4 End Sub - Public Function Method4() As Integer + Public Function {|#3:Method4|}() As Integer Return Zero End Function - Public ReadOnly Property Property1 As Integer + Public ReadOnly Property {|#4:Property1|} As Integer Get Return 5 End Get End Property - Public WriteOnly Property Property2 As Integer + Public WriteOnly Property {|#5:Property2|} As Integer Set s_field = Value End Set End Property - Public Property MyProperty As Integer + Public Property {|#6:MyProperty|} As Integer Get Return 10 End Get @@ -117,7 +116,7 @@ End Get End Set End Property - Public Custom Event CustomEvent As EventHandler(Of EventArgs) + Public Custom Event {|#7:CustomEvent|} As EventHandler(Of EventArgs) AddHandler(value As EventHandler(Of EventArgs)) End AddHandler RemoveHandler(value As EventHandler(Of EventArgs)) @@ -127,14 +126,14 @@ End RaiseEvent End Event End Class ", - GetBasicResultAt(8, 21, "Method1"), - GetBasicResultAt(12, 16, "Method2"), - GetBasicResultAt(15, 16, "Method3"), - GetBasicResultAt(19, 21, "Method4"), - GetBasicResultAt(23, 30, "Property1"), - GetBasicResultAt(29, 31, "Property2"), - GetBasicResultAt(35, 21, "MyProperty"), - GetBasicResultAt(44, 25, "CustomEvent")); + VerifyVB.Diagnostic().WithLocation(0).WithArguments("Method1"), + VerifyVB.Diagnostic().WithLocation(1).WithArguments("Method2"), + VerifyVB.Diagnostic().WithLocation(2).WithArguments("Method3"), + VerifyVB.Diagnostic().WithLocation(3).WithArguments("Method4"), + VerifyVB.Diagnostic().WithLocation(4).WithArguments("Property1"), + VerifyVB.Diagnostic().WithLocation(5).WithArguments("Property2"), + VerifyVB.Diagnostic().WithLocation(6).WithArguments("MyProperty"), + VerifyVB.Diagnostic().WithLocation(7).WithArguments("CustomEvent")); } [Fact] @@ -146,40 +145,40 @@ internal class MembersTests internal static int s_field; public const int Zero = 0; - public int Method1(string name) + public int {|#0:Method1|}(string name) { return name.Length; } public void Method2() { } - public void Method3() + public void {|#1:Method3|}() { s_field = 4; } - public int Method4() + public int {|#2:Method4|}() { return Zero; } - public int Property + public int {|#3:Property|} { get { return 5; } } - public int Property2 + public int {|#4:Property2|} { set { s_field = value; } } - public int MyProperty + public int {|#5:MyProperty|} { get { return 10; } set { System.Console.WriteLine(value); } } - public event System.EventHandler CustomEvent { add {} remove {} } + public event System.EventHandler {|#6:CustomEvent|} { add {} remove {} } public void Common(string arg) { @@ -202,13 +201,13 @@ public void Common(string arg) MyProperty = 10; // getter not accessed. } }", - GetCSharpResultAt(7, 16, "Method1"), - GetCSharpResultAt(14, 17, "Method3"), - GetCSharpResultAt(19, 16, "Method4"), - GetCSharpResultAt(24, 16, "Property"), - GetCSharpResultAt(29, 16, "Property2"), - GetCSharpResultAt(34, 16, "MyProperty"), - GetCSharpResultAt(40, 56, "CustomEvent")); + VerifyCS.Diagnostic().WithLocation(0).WithArguments("Method1"), + VerifyCS.Diagnostic().WithLocation(1).WithArguments("Method3"), + VerifyCS.Diagnostic().WithLocation(2).WithArguments("Method4"), + VerifyCS.Diagnostic().WithLocation(3).WithArguments("Property"), + VerifyCS.Diagnostic().WithLocation(4).WithArguments("Property2"), + VerifyCS.Diagnostic().WithLocation(5).WithArguments("MyProperty"), + VerifyCS.Diagnostic().WithLocation(6).WithArguments("CustomEvent")); } [Fact] @@ -221,34 +220,34 @@ Friend Class MembersTests Shared s_field As Integer Public Const Zero As Integer = 0 - Public Function Method1(name As String) As Integer + Public Function {|#0:Method1|}(name As String) As Integer Return name.Length End Function Public Sub Method2() End Sub - Public Sub Method3() + Public Sub {|#1:Method3|}() s_field = 4 End Sub - Public Function Method4() As Integer + Public Function {|#2:Method4|}() As Integer Return Zero End Function - Public ReadOnly Property Property1 As Integer + Public ReadOnly Property {|#3:Property1|} As Integer Get Return 5 End Get End Property - Public WriteOnly Property Property2 As Integer + Public WriteOnly Property {|#4:Property2|} As Integer Set s_field = Value End Set End Property - Public Property MyProperty As Integer + Public Property {|#5:MyProperty|} As Integer Get Return 10 End Get @@ -257,7 +256,7 @@ End Get End Set End Property - Public Custom Event CustomEvent As EventHandler(Of EventArgs) + Public Custom Event {|#6:CustomEvent|} As EventHandler(Of EventArgs) AddHandler(value As EventHandler(Of EventArgs)) End AddHandler RemoveHandler(value As EventHandler(Of EventArgs)) @@ -288,13 +287,13 @@ End Sub End Class ", - GetBasicResultAt(8, 21, "Method1"), - GetBasicResultAt(15, 16, "Method3"), - GetBasicResultAt(19, 21, "Method4"), - GetBasicResultAt(23, 30, "Property1"), - GetBasicResultAt(29, 31, "Property2"), - GetBasicResultAt(35, 21, "MyProperty"), - GetBasicResultAt(44, 25, "CustomEvent")); + VerifyVB.Diagnostic().WithLocation(0).WithArguments("Method1"), + VerifyVB.Diagnostic().WithLocation(1).WithArguments("Method3"), + VerifyVB.Diagnostic().WithLocation(2).WithArguments("Method4"), + VerifyVB.Diagnostic().WithLocation(3).WithArguments("Property1"), + VerifyVB.Diagnostic().WithLocation(4).WithArguments("Property2"), + VerifyVB.Diagnostic().WithLocation(5).WithArguments("MyProperty"), + VerifyVB.Diagnostic().WithLocation(6).WithArguments("CustomEvent")); } [Fact] @@ -699,14 +698,14 @@ private void M() N(); } - private void N() + private void {|#0:N|}() { } }", }, ExpectedDiagnostics = { - GetCSharpResultAt(12, 18, "N") + VerifyCS.Diagnostic().WithLocation(0).WithArguments("N"), } } }.RunAsync(); @@ -748,7 +747,7 @@ public async Task CSharp_InstanceReferenceInObjectInitializer_Diagnostic() await VerifyCS.VerifyAnalyzerAsync(@" public class A { - public void M() + public void {|#0:M|}() { var x = new B() { P = true }; } @@ -759,7 +758,7 @@ public class B public bool P { get; set; } }", // Test0.cs(4,17): warning CA1822: Member M does not access instance data and can be marked as static (Shared in VisualBasic) - GetCSharpResultAt(4, 17, "M")); + VerifyCS.Diagnostic().WithLocation(0).WithArguments("M")); } [Fact, WorkItem(1865, "https://github.com/dotnet/roslyn-analyzers/issues/1865")] @@ -767,7 +766,7 @@ public async Task Basic_InstanceReferenceInObjectInitializer_Diagnostic() { await VerifyVB.VerifyAnalyzerAsync(@" Public Class A - Public Sub M() + Public Sub {|#0:M|}() Dim x = New B With {.P = True} End Sub End Class @@ -777,7 +776,7 @@ Public Property P As Boolean End Class ", // Test0.vb(3,16): warning CA1822: Member M does not access instance data and can be marked as static (Shared in VisualBasic) - GetBasicResultAt(3, 16, "M")); + VerifyVB.Diagnostic().WithLocation(0).WithArguments("M")); } [Fact, WorkItem(1933, "https://github.com/dotnet/roslyn-analyzers/issues/1933")] @@ -1462,19 +1461,5 @@ Private Sub M() End Sub End Class"); } - - private DiagnosticResult GetCSharpResultAt(int line, int column, string symbolName) -#pragma warning disable RS0030 // Do not used banned APIs - => VerifyCS.Diagnostic() - .WithLocation(line, column) -#pragma warning restore RS0030 // Do not used banned APIs - .WithArguments(symbolName); - - private static DiagnosticResult GetBasicResultAt(int line, int column, string symbolName) -#pragma warning disable RS0030 // Do not used banned APIs - => VerifyVB.Diagnostic() - .WithLocation(line, column) -#pragma warning restore RS0030 // Do not used banned APIs - .WithArguments(symbolName); } } From 35ad7ce19173a0496d0c4422930b55f1fe3dbe86 Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Fri, 30 Apr 2021 17:49:36 -0700 Subject: [PATCH 190/224] handle params array in the argument list fixes #4842 --- ...ardCancellationTokenToInvocations.Fixer.cs | 40 ++- ...ellationTokenToInvocationsFixer.Visitor.cs | 236 +++++++++++++ ...ardCancellationTokenToInvocations.Fixer.cs | 32 +- .../Core/NullableSyntaxAnnotationEx.cs | 23 ++ ...wardCancellationTokenToInvocationsTests.cs | 89 +++++ ...ardCancellationTokenToInvocations.Fixer.vb | 23 +- ...ellationTokenToInvocationsFixer.Visitor.vb | 311 ++++++++++++++++++ .../Compiler/Lightup/ITypeSymbolExtensions.cs | 7 + 8 files changed, 745 insertions(+), 16 deletions(-) create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs create mode 100644 src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs index b418a21c59..dbdebf32f1 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs @@ -1,19 +1,23 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; +using Analyzer.Utilities.Lightup; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; using Microsoft.NetCore.Analyzers.Runtime; +using NullableAnnotation = Analyzer.Utilities.Lightup.NullableAnnotation; namespace Microsoft.NetCore.CSharp.Analyzers.Runtime { [ExportCodeFixProvider(LanguageNames.CSharp)] - public sealed class CSharpForwardCancellationTokenToInvocationsFixer : ForwardCancellationTokenToInvocationsFixer + public sealed partial class CSharpForwardCancellationTokenToInvocationsFixer : ForwardCancellationTokenToInvocationsFixer { protected override bool TryGetInvocation( SemanticModel model, @@ -46,18 +50,46 @@ protected override SyntaxNode GetConditionalOperationInvocationExpression(Syntax protected override bool TryGetExpressionAndArguments( SyntaxNode invocationNode, [NotNullWhen(returnValue: true)] out SyntaxNode? expression, - out ImmutableArray arguments) + out ImmutableArray arguments) { if (invocationNode is InvocationExpressionSyntax invocationExpression) { expression = invocationExpression.Expression; - arguments = ImmutableArray.CreateRange(invocationExpression.ArgumentList.Arguments); + arguments = invocationExpression.ArgumentList.Arguments.ToImmutableArray(); return true; } expression = null; - arguments = ImmutableArray.Empty; + arguments = ImmutableArray.Empty; return false; } + + + protected override SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type) + { + var typeName = Visitor.GenerateTypeSyntax(type.ElementType); + if (type.ElementType.IsReferenceType) + { + var additionalAnnotation = type.NullableAnnotation() switch + { + NullableAnnotation.None => NullableSyntaxAnnotationEx.Oblivious, + NullableAnnotation.Annotated => NullableSyntaxAnnotationEx.AnnotatedOrNotAnnotated, + NullableAnnotation.NotAnnotated => NullableSyntaxAnnotationEx.AnnotatedOrNotAnnotated, + _ => null, + }; + + if (additionalAnnotation is not null) + { + typeName = typeName.WithAdditionalAnnotations(additionalAnnotation); + } + } + + return typeName; + } + + protected override IEnumerable GetExpressions(ImmutableArray newArguments) + { + return newArguments.Select(x => x.Expression); + } } } diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs new file mode 100644 index 0000000000..ba36af9be6 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Analyzer.Utilities.Lightup; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Simplification; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using NullableAnnotation = Analyzer.Utilities.Lightup.NullableAnnotation; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + public sealed partial class CSharpForwardCancellationTokenToInvocationsFixer + { + private class Visitor : SymbolVisitor + { + public static TypeSyntax GenerateTypeSyntax(INamespaceOrTypeSymbol symbol) + { + return symbol.Accept(new Visitor()).WithAdditionalAnnotations(Simplifier.Annotation); + } + + public override TypeSyntax DefaultVisit(ISymbol symbol) + => throw new NotImplementedException(); + + public override TypeSyntax VisitAlias(IAliasSymbol symbol) + { + return AddInformationTo(ToIdentifierName(symbol.Name)); + } + + public override TypeSyntax VisitDynamicType(IDynamicTypeSymbol symbol) + { + return AddInformationTo(IdentifierName("dynamic")); + } + + public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol) + { + if (TryCreateNativeIntegerType(symbol, out var typeSyntax)) + return typeSyntax; + + typeSyntax = CreateSimpleTypeSyntax(symbol); + if (!(typeSyntax is SimpleNameSyntax)) + return typeSyntax; + + var simpleNameSyntax = (SimpleNameSyntax)typeSyntax; + if (symbol.ContainingType is not null) + { + if (symbol.ContainingType.TypeKind != TypeKind.Submission) + { + var containingTypeSyntax = symbol.ContainingType.Accept(this); + if (containingTypeSyntax is NameSyntax name) + { + typeSyntax = AddInformationTo( + QualifiedName(name, simpleNameSyntax)); + } + else + { + typeSyntax = AddInformationTo(simpleNameSyntax); + } + } + } + else if (symbol.ContainingNamespace is not null) + { + if (symbol.ContainingNamespace.IsGlobalNamespace) + { + if (symbol.TypeKind != TypeKind.Error) + { + typeSyntax = AddGlobalAlias(simpleNameSyntax); + } + } + else + { + var container = symbol.ContainingNamespace.Accept(this)!; + typeSyntax = AddInformationTo(QualifiedName( + (NameSyntax)container, + simpleNameSyntax)); + } + } + + if (symbol.NullableAnnotation() == NullableAnnotation.Annotated && + !symbol.IsValueType) + { + typeSyntax = AddInformationTo(NullableType(typeSyntax)); + } + + return typeSyntax; + } + + public override TypeSyntax VisitNamespace(INamespaceSymbol symbol) + { + var syntax = AddInformationTo(ToIdentifierName(symbol.Name)); + if (symbol.ContainingNamespace == null) + { + return syntax; + } + + if (symbol.ContainingNamespace.IsGlobalNamespace) + { + return AddGlobalAlias(syntax); + } + else + { + var container = symbol.ContainingNamespace.Accept(this)!; + return AddInformationTo(QualifiedName( + (NameSyntax)container, + syntax)); + } + } + + public override TypeSyntax VisitTypeParameter(ITypeParameterSymbol symbol) + { + TypeSyntax typeSyntax = AddInformationTo(ToIdentifierName(symbol.Name)); + if (symbol.NullableAnnotation() == NullableAnnotation.Annotated) + typeSyntax = AddInformationTo(NullableType(typeSyntax)); + + return typeSyntax; + } + + private TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol) + { + if (symbol.IsTupleType && symbol.TupleUnderlyingType != null && !symbol.Equals(symbol.TupleUnderlyingType)) + { + return CreateSimpleTypeSyntax(symbol.TupleUnderlyingType); + } + + if (string.IsNullOrEmpty(symbol.Name) || symbol.IsAnonymousType) + { + return CreateSystemObject(); + } + + if (symbol.TypeParameters.Length == 0) + { + if (symbol.TypeKind == TypeKind.Error && symbol.Name == "var") + { + return CreateSystemObject(); + } + + return ToIdentifierName(symbol.Name); + } + + var typeArguments = symbol.IsUnboundGenericType + ? Enumerable.Repeat((TypeSyntax)OmittedTypeArgument(), symbol.TypeArguments.Length) + : symbol.TypeArguments.Select(t => GenerateTypeSyntax(t)); + + return GenericName( + ToIdentifierToken(symbol.Name), + TypeArgumentList(SeparatedList(typeArguments))); + } + + private static QualifiedNameSyntax CreateSystemObject() + { + return QualifiedName( + AliasQualifiedName( + CreateGlobalIdentifier(), + IdentifierName("System")), + IdentifierName("Object")); + } + + private static TTypeSyntax AddInformationTo(TTypeSyntax syntax) + where TTypeSyntax : TypeSyntax + { + syntax = syntax.WithLeadingTrivia(ElasticMarker).WithTrailingTrivia(ElasticMarker); + return syntax; + } + + /// + /// We always unilaterally add "global::" to all named types/namespaces. This + /// will then be trimmed off if possible by the simplifier + /// + private static TypeSyntax AddGlobalAlias(SimpleNameSyntax syntax) + { + return AddInformationTo(AliasQualifiedName(CreateGlobalIdentifier(), syntax)); + } + + private static IdentifierNameSyntax ToIdentifierName(string identifier) + => IdentifierName(ToIdentifierToken(identifier)); + + private static IdentifierNameSyntax CreateGlobalIdentifier() + => IdentifierName(Token(SyntaxKind.GlobalKeyword)); + + private static bool TryCreateNativeIntegerType(INamedTypeSymbol symbol, [NotNullWhen(true)] out TypeSyntax? syntax) + { + if (symbol.IsNativeIntegerType()) + { + syntax = IdentifierName(symbol.SpecialType == SpecialType.System_IntPtr ? "nint" : "nuint"); + return true; + } + + syntax = null; + return false; + } + + private static SyntaxToken ToIdentifierToken(string identifier, bool isQueryContext = false) + { + var escaped = EscapeIdentifier(identifier, isQueryContext); + + if (escaped.Length == 0 || escaped[0] != '@') + { + return Identifier(escaped); + } + + var unescaped = identifier.StartsWith("@", StringComparison.Ordinal) + ? identifier[1..] + : identifier; + + var token = Identifier( + default, SyntaxKind.None, "@" + unescaped, unescaped, default); + + if (!identifier.StartsWith("@", StringComparison.Ordinal)) + { + token = token.WithAdditionalAnnotations(Simplifier.Annotation); + } + + return token; + } + + private static string EscapeIdentifier(string identifier, bool isQueryContext = false) + { + var nullIndex = identifier.IndexOf('\0'); + if (nullIndex >= 0) + { + identifier = identifier.Substring(0, nullIndex); + } + + var needsEscaping = SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None; + + // Check if we need to escape this contextual keyword + needsEscaping = needsEscaping || (isQueryContext && SyntaxFacts.IsQueryContextualKeyword(SyntaxFacts.GetContextualKeywordKind(identifier))); + + return needsEscaping ? "@" + identifier : identifier; + } + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index fef3a2e06b..b70ddb909d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; @@ -13,7 +15,8 @@ namespace Microsoft.NetCore.Analyzers.Runtime { - public abstract class ForwardCancellationTokenToInvocationsFixer : CodeFixProvider + public abstract class ForwardCancellationTokenToInvocationsFixer : CodeFixProvider + where TArgumentSyntax : SyntaxNode { // Attempts to retrieve the invocation from the current operation. protected abstract bool TryGetInvocation( @@ -26,7 +29,7 @@ protected abstract bool TryGetInvocation( protected abstract bool TryGetExpressionAndArguments( SyntaxNode invocationNode, [NotNullWhen(returnValue: true)] out SyntaxNode? expression, - out ImmutableArray arguments); + out ImmutableArray arguments); // Verifies if the specified argument was passed with an explicit name. protected abstract bool IsArgumentNamed(IArgumentOperation argumentOperation); @@ -34,6 +37,9 @@ protected abstract bool TryGetExpressionAndArguments( // Retrieves the invocation expression for a conditional operation, which consists of the dot and the method name. protected abstract SyntaxNode GetConditionalOperationInvocationExpression(SyntaxNode invocationNode); + protected abstract SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type); + protected abstract IEnumerable GetExpressions(ImmutableArray newArguments); + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ForwardCancellationTokenToInvocationsAnalyzer.RuleId); @@ -83,14 +89,15 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle; - if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out ImmutableArray newArguments)) + if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out ImmutableArray newArguments)) { return; } + var paramsArrayType = invocation.Arguments.SingleOrDefault(a => a.ArgumentKind == ArgumentKind.ParamArray)?.Value.Type as IArrayTypeSymbol; Task CreateChangedDocumentAsync(CancellationToken _) { - SyntaxNode newRoot = TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, expression, newArguments); + SyntaxNode newRoot = TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, expression, newArguments, paramsArrayType); Document newDocument = doc.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument); } @@ -110,10 +117,25 @@ private static SyntaxNode TryGenerateNewDocumentRoot( string invocationTokenArgumentName, string ancestorTokenParameterName, SyntaxNode expression, - ImmutableArray newArguments) + ImmutableArray currentArguments, + IArrayTypeSymbol? paramsArrayType) { SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); + ImmutableArray newArguments; + if (paramsArrayType is not null) + { + // current callsite is a params array, we need to wrap all these arguments to preserve sematnics + var typeSyntax = GetTypeSyntaxForArray(paramsArrayType); + var expressions = GetExpressions(currentArguments); + newArguments = ImmutableArray.Create(generator.ArrayCreationExpression(typeSyntax, expressions)); + } + else + { + // not a params array just pass the existing arguments along + newArguments = currentArguments.CastArray(); + } + SyntaxNode identifier = generator.IdentifierName(invocationTokenArgumentName); SyntaxNode cancellationTokenArgument; if (!string.IsNullOrEmpty(ancestorTokenParameterName)) diff --git a/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs b/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs new file mode 100644 index 0000000000..b3baea62a0 --- /dev/null +++ b/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace Analyzer.Utilities.Lightup +{ + internal static class NullableSyntaxAnnotationEx + { + public static SyntaxAnnotation? Oblivious { get; } + public static SyntaxAnnotation? AnnotatedOrNotAnnotated { get; } + + static NullableSyntaxAnnotationEx() + { + var nullableSyntaxAnnotation = typeof(Workspace).Assembly.GetType("Microsoft.CodeAnalysis.CodeGeneration.NullableSyntaxAnnotation", throwOnError: false); + if (nullableSyntaxAnnotation is object) + { + Oblivious = (SyntaxAnnotation?)nullableSyntaxAnnotation.GetField(nameof(Oblivious), BindingFlags.Static | BindingFlags.Public)?.GetValue(null); + AnnotatedOrNotAnnotated = (SyntaxAnnotation?)nullableSyntaxAnnotation.GetField(nameof(AnnotatedOrNotAnnotated), BindingFlags.Static | BindingFlags.Public)?.GetValue(null); + } + } + } +} diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index ee9d373715..2cd821e315 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -2479,6 +2479,48 @@ static async Task M1Async(string s, CancellationToken cancellationToken) return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + [WorkItem(4842, "https://github.com/dotnet/roslyn-analyzers/issues/4842")] + public Task CS_ParamsArray() + { + // Local static functions are available in C# >= 8.0 + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; + +public class C{ + public ValueTask FindAsync(params object[] keyValues) => throw new NotImplementedException(); + public ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) => throw new NotImplementedException(); +} + +public class B { + async Task M(string[] args, CancellationToken token) + { + var c = new C(); + var result = await [|c.FindAsync|](5); + } +}"; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; + +public class C{ + public ValueTask FindAsync(params object[] keyValues) => throw new NotImplementedException(); + public ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) => throw new NotImplementedException(); +} + +public class B { + async Task M(string[] args, CancellationToken token) + { + var c = new C(); + var result = await c.FindAsync(new object[] { 5 }, cancellationToken: token); + } +}"; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region No Diagnostic - VB @@ -4637,6 +4679,53 @@ End Module return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + [WorkItem(4842, "https://github.com/dotnet/roslyn-analyzers/issues/4842")] + public Task VB_ParamsArray() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks + +Public Class C + Public Function FindAsync(ParamArray keyValues() As Object) As Task(Of Object) + Throw New NotImplementedException() + End Function + + Public Function FindAsync(keyValues() As Object, cancellationToken As CancellationToken) As Task(Of Object) + Throw New NotImplementedException() + End Function + + Async Function M(args As String(), cancellationToken As CancellationToken) As Task + Dim c = New C() + Dim result = Await c.[|FindAsync|](5) + End Function +End Class +"; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks + +Public Class C + Public Function FindAsync(ParamArray keyValues() As Object) As Task(Of Object) + Throw New NotImplementedException() + End Function + + Public Function FindAsync(keyValues() As Object, cancellationToken As CancellationToken) As Task(Of Object) + Throw New NotImplementedException() + End Function + + Async Function M(args As String(), cancellationToken As CancellationToken) As Task + Dim c = New C() + Dim result = Await c.FindAsync(New Object() {5}, cancellationToken:=cancellationToken) + End Function +End Class +"; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region Helpers diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb index a670e8d27b..8363052b2a 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb @@ -5,16 +5,18 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Operations +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.NetCore.Analyzers.Runtime Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime - Public NotInheritable Class BasicForwardCancellationTokenToInvocationsFixer - - Inherits ForwardCancellationTokenToInvocationsFixer + Partial Public NotInheritable Class BasicForwardCancellationTokenToInvocationsFixer + Inherits ForwardCancellationTokenToInvocationsFixer(Of ArgumentSyntax) Protected Overrides Function TryGetInvocation(model As SemanticModel, node As SyntaxNode, ct As CancellationToken, ByRef invocation As IInvocationOperation) As Boolean @@ -46,24 +48,31 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End Function - Protected Overrides Function TryGetExpressionAndArguments(invocationNode As SyntaxNode, ByRef expression As SyntaxNode, ByRef arguments As ImmutableArray(Of SyntaxNode)) As Boolean + Protected Overrides Function TryGetExpressionAndArguments(invocationNode As SyntaxNode, ByRef expression As SyntaxNode, ByRef arguments As ImmutableArray(Of ArgumentSyntax)) As Boolean Dim invocationExpression As InvocationExpressionSyntax = TryCast(invocationNode, InvocationExpressionSyntax) If invocationExpression IsNot Nothing Then expression = invocationExpression.Expression - arguments = ImmutableArray.CreateRange(Of SyntaxNode)(invocationExpression.ArgumentList.Arguments) + arguments = invocationExpression.ArgumentList.Arguments.ToImmutableArray Return True End If expression = Nothing - arguments = ImmutableArray(Of SyntaxNode).Empty + arguments = ImmutableArray(Of ArgumentSyntax).Empty Return False End Function - End Class + Protected Overrides Function GetTypeSyntaxForArray(type As IArrayTypeSymbol) As SyntaxNode + Return Visitor.GenerateTypeSyntax(type.ElementType) + End Function + Protected Overrides Function GetExpressions(newArguments As ImmutableArray(Of ArgumentSyntax)) As IEnumerable(Of SyntaxNode) + Return From argument In newArguments + Select argument.GetExpression() + End Function + End Class End Namespace diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb new file mode 100644 index 0000000000..cbf6497a49 --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb @@ -0,0 +1,311 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + Partial Public NotInheritable Class BasicForwardCancellationTokenToInvocationsFixer + Private Class Visitor + Inherits SymbolVisitor(Of TypeSyntax) + + Public Shared Function GenerateTypeSyntax(symbol As INamespaceOrTypeSymbol) As TypeSyntax + Return symbol.Accept(New Visitor()).WithAdditionalAnnotations(Simplifier.Annotation) + End Function + + Public Overrides Function DefaultVisit(symbol As ISymbol) As TypeSyntax + Throw New NotImplementedException() + End Function + + Public Overrides Function VisitAlias(symbol As IAliasSymbol) As TypeSyntax + Return AddInformationTo(ToIdentifierName(symbol.Name)) + End Function + + Public Overrides Function VisitArrayType(symbol As IArrayTypeSymbol) As TypeSyntax + Dim underlyingNonArrayType = symbol.ElementType + While underlyingNonArrayType.Kind = SymbolKind.ArrayType + underlyingNonArrayType = DirectCast(underlyingNonArrayType, IArrayTypeSymbol).ElementType + End While + + Dim elementTypeSyntax = underlyingNonArrayType.Accept(Me) + Dim ranks = New List(Of ArrayRankSpecifierSyntax)() + Dim arrayType = symbol + While arrayType IsNot Nothing + Dim commaCount = Math.Max(0, arrayType.Rank - 1) + Dim commas = SyntaxFactory.TokenList(Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), commaCount)) + ranks.Add(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.Token(SyntaxKind.OpenParenToken), commas, SyntaxFactory.Token(SyntaxKind.CloseParenToken))) + arrayType = TryCast(arrayType.ElementType, IArrayTypeSymbol) + End While + + Dim arrayTypeSyntax = SyntaxFactory.ArrayType(elementTypeSyntax, SyntaxFactory.List(ranks)) + Return AddInformationTo(arrayTypeSyntax) + End Function + + Public Overrides Function VisitDynamicType(symbol As IDynamicTypeSymbol) As TypeSyntax + Return AddInformationTo(SyntaxFactory.IdentifierName("dynamic")) + End Function + + Public Overrides Function VisitNamedType(symbol As INamedTypeSymbol) As TypeSyntax + Dim typeSyntax = CreateSimpleTypeSyntax(symbol) + If Not (TypeOf typeSyntax Is SimpleNameSyntax) Then + Return typeSyntax + End If + + Dim simpleNameSyntax = DirectCast(typeSyntax, SimpleNameSyntax) + If symbol.ContainingType IsNot Nothing Then + If symbol.ContainingType.TypeKind = TypeKind.Submission Then + Return typeSyntax + Else + Return AddInformationTo(SyntaxFactory.QualifiedName(DirectCast(symbol.ContainingType.Accept(Me), NameSyntax), simpleNameSyntax)) + End If + ElseIf symbol.ContainingNamespace IsNot Nothing Then + If symbol.ContainingNamespace.IsGlobalNamespace Then + If symbol.TypeKind <> TypeKind.[Error] Then + Return AddInformationTo(SyntaxFactory.QualifiedName(SyntaxFactory.GlobalName(), simpleNameSyntax)) + End If + Else + Dim container = symbol.ContainingNamespace.Accept(Me) + Return AddInformationTo(SyntaxFactory.QualifiedName(DirectCast(container, NameSyntax), simpleNameSyntax)) + End If + End If + + Return simpleNameSyntax + End Function + + Public Overrides Function VisitNamespace(symbol As INamespaceSymbol) As TypeSyntax + Dim result = AddInformationTo(ToIdentifierName(symbol.Name)) + If symbol.ContainingNamespace Is Nothing Then + Return result + End If + + If symbol.ContainingNamespace.IsGlobalNamespace Then + Return AddInformationTo(SyntaxFactory.QualifiedName(SyntaxFactory.GlobalName(), result)) + Else + Dim container = symbol.ContainingNamespace.Accept(Me) + Return AddInformationTo(SyntaxFactory.QualifiedName(DirectCast(container, NameSyntax), result)) + End If + End Function + + Public Overrides Function VisitPointerType(symbol As IPointerTypeSymbol) As TypeSyntax + Return symbol.PointedAtType.Accept(Me) + End Function + + Public Overrides Function VisitTypeParameter(symbol As ITypeParameterSymbol) As TypeSyntax + Return AddInformationTo(ToIdentifierName(symbol.Name)) + End Function + + Private Shared Function AddInformationTo(Of TTypeSyntax As TypeSyntax)(type As TTypeSyntax) As TTypeSyntax + type = type.WithLeadingTrivia(SyntaxFactory.ElasticMarker).WithTrailingTrivia(SyntaxFactory.ElasticMarker) + Return type + End Function + + Private Shared Function CreateSimpleTypeSyntax(symbol As INamedTypeSymbol) As TypeSyntax + Dim syntax = TryCreateSpecializedNamedTypeSyntax(symbol) + If syntax IsNot Nothing Then + Return syntax + End If + + If symbol.IsTupleType AndAlso + symbol.TupleUnderlyingType IsNot Nothing AndAlso + Not symbol.Equals(symbol.TupleUnderlyingType) Then + Return CreateSimpleTypeSyntax(symbol.TupleUnderlyingType) + End If + + If symbol.Name = String.Empty OrElse symbol.IsAnonymousType Then + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Object")) + End If + + If symbol.OriginalDefinition.SpecialType = SpecialType.System_Nullable_T Then + Return AddInformationTo(SyntaxFactory.NullableType(symbol.TypeArguments.First().Accept(New Visitor()))) + End If + + If symbol.TypeParameters.Length = 0 Then + Return ToIdentifierName(symbol.Name) + End If + + Return SyntaxFactory.GenericName( + ToIdentifierToken(symbol.Name), + SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(symbol.TypeArguments.[Select](Function(t) t.Accept(New Visitor()))))) + End Function + + Private Shared Function TryCreateSpecializedNamedTypeSyntax(symbol As INamedTypeSymbol) As TypeSyntax + Select Case symbol.SpecialType + Case SpecialType.System_Object + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Object")) + Case SpecialType.System_Boolean + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Boolean")) + Case SpecialType.System_Char + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Char")) + Case SpecialType.System_SByte + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("SByte")) + Case SpecialType.System_Byte + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Byte")) + Case SpecialType.System_Int16 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Int16")) + Case SpecialType.System_UInt16 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("UInt16")) + Case SpecialType.System_Int32 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Int32")) + Case SpecialType.System_Int64 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Int64")) + Case SpecialType.System_UInt32 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("UInt32")) + Case SpecialType.System_UInt64 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("UInt64")) + Case SpecialType.System_Decimal + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Decimal")) + Case SpecialType.System_Single + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Single")) + Case SpecialType.System_Double + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Double")) + Case SpecialType.System_String + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("String")) + Case SpecialType.System_DateTime + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("DateTime")) + End Select + + If symbol.IsTupleType AndAlso symbol.TupleElements.Length >= 2 Then + Return CreateTupleTypeSyntax(symbol) + End If + + Return Nothing + End Function + + Private Shared Function CreateTupleTypeSyntax(symbol As INamedTypeSymbol) As TypeSyntax + Dim elements = symbol.TupleElements + + Return SyntaxFactory.TupleType(SyntaxFactory.SeparatedList( + elements.Select(Function(element) If(Not element.IsImplicitlyDeclared, + SyntaxFactory.NamedTupleElement( + SyntaxFactory.Identifier(element.Name), + SyntaxFactory.SimpleAsClause( + SyntaxFactory.Token(SyntaxKind.AsKeyword), + Nothing, + GenerateTypeSyntax(element.Type))), + DirectCast(SyntaxFactory.TypedTupleElement( + GenerateTypeSyntax(element.Type)), TupleElementSyntax))))) + End Function + + Private Shared Function ToIdentifierName(text As String) As IdentifierNameSyntax + Return SyntaxFactory.IdentifierName(ToIdentifierToken(text)).WithAdditionalAnnotations(Simplifier.Annotation) + End Function + + Private Shared Function ToIdentifierToken(text As String, Optional afterDot As Boolean = False, Optional symbol As ISymbol = Nothing, Optional withinAsyncMethod As Boolean = False) As SyntaxToken + + Dim unescaped = text + Dim wasAlreadyEscaped = False + + If text.Length > 2 AndAlso MakeHalfWidthIdentifier(text.First()) = "[" AndAlso MakeHalfWidthIdentifier(text.Last()) = "]" Then + unescaped = text.Substring(1, text.Length() - 2) + wasAlreadyEscaped = True + End If + + Dim escaped = EscapeIdentifier(text, afterDot, symbol, withinAsyncMethod) + Dim token = If(escaped.Length > 0 AndAlso escaped(0) = "["c, + SyntaxFactory.Identifier(escaped, isBracketed:=True, identifierText:=unescaped, typeCharacter:=TypeCharacter.None), + SyntaxFactory.Identifier(text)) + + If Not wasAlreadyEscaped Then + token = token.WithAdditionalAnnotations(Simplifier.Annotation) + End If + + Return token + End Function + + Private Shared Function EscapeIdentifier(text As String, Optional afterDot As Boolean = False, Optional symbol As ISymbol = Nothing, Optional withinAsyncMethod As Boolean = False) As String + Dim keywordKind = SyntaxFacts.GetKeywordKind(text) + Dim needsEscaping = keywordKind <> SyntaxKind.None + + ' REM and New must always be escaped, but there are some conditions where + ' keywords are not escaped + If needsEscaping AndAlso + keywordKind <> SyntaxKind.REMKeyword AndAlso + keywordKind <> SyntaxKind.NewKeyword Then + + needsEscaping = Not afterDot + + If needsEscaping Then + Dim typeSymbol = TryCast(symbol, ITypeSymbol) + needsEscaping = typeSymbol Is Nothing OrElse Not IsPredefinedType(typeSymbol) + End If + End If + + ' GetKeywordKind won't return SyntaxKind.AwaitKeyword (943836) + If withinAsyncMethod AndAlso text = "Await" Then + needsEscaping = True + End If + + Return If(needsEscaping, "[" & text & "]", text) + End Function + + Private Shared Function IsPredefinedType(type As ITypeSymbol) As Boolean + Select Case type.SpecialType + Case SpecialType.System_Boolean, + SpecialType.System_Byte, + SpecialType.System_SByte, + SpecialType.System_Int16, + SpecialType.System_UInt16, + SpecialType.System_Int32, + SpecialType.System_UInt32, + SpecialType.System_Int64, + SpecialType.System_UInt64, + SpecialType.System_Single, + SpecialType.System_Double, + SpecialType.System_Decimal, + SpecialType.System_DateTime, + SpecialType.System_Char, + SpecialType.System_String, + SpecialType.System_Object + Return True + Case Else + Return False + End Select + End Function + + ''' + ''' Creates a half width form Unicode character string. + ''' + ''' The text representing the original identifier. This can be in full width or half width Unicode form. + ''' A string representing the text in a half width Unicode form. + Private Shared Function MakeHalfWidthIdentifier(text As String) As String + If text Is Nothing Then + Return text + End If + + Dim characters As Char() = Nothing + For i = 0 To text.Length - 1 + Dim c = text(i) + + If IsFullWidth(c) Then + If characters Is Nothing Then + characters = New Char(text.Length - 1) {} + text.CopyTo(0, characters, 0, i) + End If + + characters(i) = MakeHalfWidth(c) + ElseIf characters IsNot Nothing Then + characters(i) = c + End If + Next + + Return If(characters Is Nothing, text, New String(characters)) + End Function + + Private Const s_fullwidth = &HFF00L - &H20L + + '// IsFullWidth - Returns if the character is full width + Private Shared Function IsFullWidth(c As Char) As Boolean + ' Do not use "AndAlso" or it will not inline. + Return c > ChrW(&HFF00US) And c < ChrW(&HFF5FUS) + End Function + + '// MakeHalfWidth - Converts a full-width character to half-width + Friend Shared Function MakeHalfWidth(c As Char) As Char + Debug.Assert(IsFullWidth(c)) + + Return Convert.ToChar(Convert.ToUInt16(c) - s_fullwidth) + End Function + End Class + End Class +End Namespace diff --git a/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs b/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs index 5938627409..04d9ccb445 100644 --- a/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs +++ b/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Reflection; using Microsoft.CodeAnalysis; namespace Analyzer.Utilities.Lightup @@ -13,10 +14,16 @@ private static readonly Func s_nullableAnnotati private static readonly Func s_withNullableAnnotation = LightupHelpers.CreateSymbolWithPropertyAccessor(typeof(ITypeSymbol), nameof(NullableAnnotation), fallbackResult: Lightup.NullableAnnotation.None); + private static readonly Func s_isNativeIntegerType + = LightupHelpers.CreateSymbolPropertyAccessor(typeof(ITypeSymbol), nameof(IsNativeIntegerType), fallbackResult: false); + public static NullableAnnotation NullableAnnotation(this ITypeSymbol typeSymbol) => s_nullableAnnotation(typeSymbol); public static ITypeSymbol WithNullableAnnotation(this ITypeSymbol typeSymbol, NullableAnnotation nullableAnnotation) => s_withNullableAnnotation(typeSymbol, nullableAnnotation); + + public static bool IsNativeIntegerType(this ITypeSymbol typeSymbol) + => s_isNativeIntegerType(typeSymbol); } } From 3651a9ff4340e0819dbb902a09469fb5b7dafcfe Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 3 May 2021 06:43:21 -0700 Subject: [PATCH 191/224] Update src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs Co-authored-by: Youssef Victor --- .../Runtime/ForwardCancellationTokenToInvocationsTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 2cd821e315..9e677d85ef 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -2483,7 +2483,6 @@ static async Task M1Async(string s, CancellationToken cancellationToken) [WorkItem(4842, "https://github.com/dotnet/roslyn-analyzers/issues/4842")] public Task CS_ParamsArray() { - // Local static functions are available in C# >= 8.0 string originalCode = @" using System; using System.Threading; @@ -4786,4 +4785,4 @@ private static async Task VB16VerifyAnalyzerAsync(string originalCode) #endregion } -} \ No newline at end of file +} From db392cc2ba67264c897ec039c58e2d6bd5fcdf70 Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 3 May 2021 07:32:47 -0700 Subject: [PATCH 192/224] remove unused parameters --- ...ForwardCancellationTokenToInvocationsFixer.Visitor.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs index ba36af9be6..b57c430393 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs @@ -192,9 +192,9 @@ private static bool TryCreateNativeIntegerType(INamedTypeSymbol symbol, [NotNull return false; } - private static SyntaxToken ToIdentifierToken(string identifier, bool isQueryContext = false) + private static SyntaxToken ToIdentifierToken(string identifier) { - var escaped = EscapeIdentifier(identifier, isQueryContext); + var escaped = EscapeIdentifier(identifier); if (escaped.Length == 0 || escaped[0] != '@') { @@ -216,7 +216,7 @@ private static SyntaxToken ToIdentifierToken(string identifier, bool isQueryCont return token; } - private static string EscapeIdentifier(string identifier, bool isQueryContext = false) + private static string EscapeIdentifier(string identifier) { var nullIndex = identifier.IndexOf('\0'); if (nullIndex >= 0) @@ -226,9 +226,6 @@ private static string EscapeIdentifier(string identifier, bool isQueryContext = var needsEscaping = SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None; - // Check if we need to escape this contextual keyword - needsEscaping = needsEscaping || (isQueryContext && SyntaxFacts.IsQueryContextualKeyword(SyntaxFacts.GetContextualKeywordKind(identifier))); - return needsEscaping ? "@" + identifier : identifier; } } From 04bc47fb7858fc3fe8aae5b1c0887532d18d40ab Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 3 May 2021 07:36:40 -0700 Subject: [PATCH 193/224] rename types --- ...pForwardCancellationTokenToInvocations.Fixer.cs | 2 +- ...dCancellationTokenToInvocationsFixer.Visitor.cs | 8 ++++---- ...cForwardCancellationTokenToInvocations.Fixer.vb | 2 +- ...dCancellationTokenToInvocationsFixer.Visitor.vb | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs index dbdebf32f1..a2cb61e482 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs @@ -67,7 +67,7 @@ protected override bool TryGetExpressionAndArguments( protected override SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type) { - var typeName = Visitor.GenerateTypeSyntax(type.ElementType); + var typeName = TypeNameVisitor.GetTypeSyntaxForSymbol(type.ElementType); if (type.ElementType.IsReferenceType) { var additionalAnnotation = type.NullableAnnotation() switch diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs index b57c430393..371446acce 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs @@ -15,11 +15,11 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Runtime { public sealed partial class CSharpForwardCancellationTokenToInvocationsFixer { - private class Visitor : SymbolVisitor + private class TypeNameVisitor : SymbolVisitor { - public static TypeSyntax GenerateTypeSyntax(INamespaceOrTypeSymbol symbol) + public static TypeSyntax GetTypeSyntaxForSymbol(INamespaceOrTypeSymbol symbol) { - return symbol.Accept(new Visitor()).WithAdditionalAnnotations(Simplifier.Annotation); + return symbol.Accept(new TypeNameVisitor()).WithAdditionalAnnotations(Simplifier.Annotation); } public override TypeSyntax DefaultVisit(ISymbol symbol) @@ -142,7 +142,7 @@ private TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol) var typeArguments = symbol.IsUnboundGenericType ? Enumerable.Repeat((TypeSyntax)OmittedTypeArgument(), symbol.TypeArguments.Length) - : symbol.TypeArguments.Select(t => GenerateTypeSyntax(t)); + : symbol.TypeArguments.Select(t => GetTypeSyntaxForSymbol(t)); return GenericName( ToIdentifierToken(symbol.Name), diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb index 8363052b2a..a50a16654a 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb @@ -67,7 +67,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End Function Protected Overrides Function GetTypeSyntaxForArray(type As IArrayTypeSymbol) As SyntaxNode - Return Visitor.GenerateTypeSyntax(type.ElementType) + Return TypeNameVisitor.GetTypeSyntaxForSymbol(type.ElementType) End Function Protected Overrides Function GetExpressions(newArguments As ImmutableArray(Of ArgumentSyntax)) As IEnumerable(Of SyntaxNode) diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb index cbf6497a49..1e6787ea70 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb @@ -7,11 +7,11 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Partial Public NotInheritable Class BasicForwardCancellationTokenToInvocationsFixer - Private Class Visitor + Private Class TypeNameVisitor Inherits SymbolVisitor(Of TypeSyntax) - Public Shared Function GenerateTypeSyntax(symbol As INamespaceOrTypeSymbol) As TypeSyntax - Return symbol.Accept(New Visitor()).WithAdditionalAnnotations(Simplifier.Annotation) + Public Shared Function GetTypeSyntaxForSymbol(symbol As INamespaceOrTypeSymbol) As TypeSyntax + Return symbol.Accept(New TypeNameVisitor()).WithAdditionalAnnotations(Simplifier.Annotation) End Function Public Overrides Function DefaultVisit(symbol As ISymbol) As TypeSyntax @@ -117,7 +117,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End If If symbol.OriginalDefinition.SpecialType = SpecialType.System_Nullable_T Then - Return AddInformationTo(SyntaxFactory.NullableType(symbol.TypeArguments.First().Accept(New Visitor()))) + Return AddInformationTo(SyntaxFactory.NullableType(symbol.TypeArguments.First().Accept(New TypeNameVisitor()))) End If If symbol.TypeParameters.Length = 0 Then @@ -126,7 +126,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return SyntaxFactory.GenericName( ToIdentifierToken(symbol.Name), - SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(symbol.TypeArguments.[Select](Function(t) t.Accept(New Visitor()))))) + SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(symbol.TypeArguments.[Select](Function(t) t.Accept(New TypeNameVisitor()))))) End Function Private Shared Function TryCreateSpecializedNamedTypeSyntax(symbol As INamedTypeSymbol) As TypeSyntax @@ -182,9 +182,9 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime SyntaxFactory.SimpleAsClause( SyntaxFactory.Token(SyntaxKind.AsKeyword), Nothing, - GenerateTypeSyntax(element.Type))), + GetTypeSyntaxForSymbol(element.Type))), DirectCast(SyntaxFactory.TypedTupleElement( - GenerateTypeSyntax(element.Type)), TupleElementSyntax))))) + GetTypeSyntaxForSymbol(element.Type)), TupleElementSyntax))))) End Function Private Shared Function ToIdentifierName(text As String) As IdentifierNameSyntax From 41a0a2ce34eedb4e60e5e36e6ee7025ac51bd013 Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 3 May 2021 07:37:49 -0700 Subject: [PATCH 194/224] rename files --- ...ForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs} | 0 ...ForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/{CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs => CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs} (100%) rename src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/{BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb => BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb} (100%) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs similarity index 100% rename from src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.Visitor.cs rename to src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb similarity index 100% rename from src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.Visitor.vb rename to src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb From 73edf913cfb66ec053e871040465b3b09583564a Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 3 May 2021 10:40:58 -0700 Subject: [PATCH 195/224] manually contruct array in VB case --- ...SharpForwardCancellationTokenToInvocations.Fixer.cs | 6 ++++++ .../ForwardCancellationTokenToInvocations.Fixer.cs | 3 ++- ...BasicForwardCancellationTokenToInvocations.Fixer.vb | 10 ++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs index a2cb61e482..10344bd8e2 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Operations; using Microsoft.NetCore.Analyzers.Runtime; using NullableAnnotation = Analyzer.Utilities.Lightup.NullableAnnotation; @@ -91,5 +92,10 @@ protected override IEnumerable GetExpressions(ImmutableArray x.Expression); } + + protected override SyntaxNode GetArrayCreationExpression(SyntaxGenerator generator, SyntaxNode typeSyntax, IEnumerable expressions) + { + return generator.ArrayCreationExpression(typeSyntax, expressions); + } } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index b70ddb909d..3928454caf 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -39,6 +39,7 @@ protected abstract bool TryGetExpressionAndArguments( protected abstract SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type); protected abstract IEnumerable GetExpressions(ImmutableArray newArguments); + protected abstract SyntaxNode GetArrayCreationExpression(SyntaxGenerator generator, SyntaxNode typeSyntax, IEnumerable expressions); public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ForwardCancellationTokenToInvocationsAnalyzer.RuleId); @@ -128,7 +129,7 @@ private static SyntaxNode TryGenerateNewDocumentRoot( // current callsite is a params array, we need to wrap all these arguments to preserve sematnics var typeSyntax = GetTypeSyntaxForArray(paramsArrayType); var expressions = GetExpressions(currentArguments); - newArguments = ImmutableArray.Create(generator.ArrayCreationExpression(typeSyntax, expressions)); + newArguments = ImmutableArray.Create(GetArrayCreationExpression(generator, typeSyntax, expressions)); } else { diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb index a50a16654a..de0f93dae1 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb @@ -5,6 +5,7 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Operations Imports Microsoft.CodeAnalysis.Simplification @@ -74,5 +75,14 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return From argument In newArguments Select argument.GetExpression() End Function + + Protected Overrides Function GetArrayCreationExpression(generator As SyntaxGenerator, typeSyntax As SyntaxNode, expressions As IEnumerable(Of SyntaxNode)) As SyntaxNode + ' VB SyntaxGenerator will create ArgumentList nodes by default but the parse creates ArrayRankSpecifier nodes + ' We contruct the syntax manually to work around this + Dim rankSpecifiers = SyntaxFactory.List(Of ArrayRankSpecifierSyntax).Add(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.Token(SyntaxKind.OpenParenToken), Nothing, SyntaxFactory.Token(SyntaxKind.CloseParenToken))) + Dim initializer = SyntaxFactory.CollectionInitializer(SyntaxFactory.SeparatedList(expressions.Cast(Of ExpressionSyntax))) + Dim arrayCreationExpression = SyntaxFactory.ArrayCreationExpression(SyntaxFactory.Token(SyntaxKind.NewKeyword), Nothing, CType(typeSyntax, TypeSyntax), Nothing, rankSpecifiers, initializer) + Return arrayCreationExpression.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation) + End Function End Class End Namespace From e10120aa1d21ce2029076164bd41243efe606879 Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 3 May 2021 14:25:00 -0700 Subject: [PATCH 196/224] remove unused using --- src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs b/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs index 04d9ccb445..726c4a9f78 100644 --- a/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs +++ b/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Reflection; using Microsoft.CodeAnalysis; namespace Analyzer.Utilities.Lightup From 492a90177db2fe50317bc42edb6598e57c596530 Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 3 May 2021 15:08:58 -0700 Subject: [PATCH 197/224] remove blank line --- .../Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs index 10344bd8e2..9e745a819d 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs @@ -65,7 +65,6 @@ protected override bool TryGetExpressionAndArguments( return false; } - protected override SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type) { var typeName = TypeNameVisitor.GetTypeSyntaxForSymbol(type.ElementType); From 2a5378f33161c06fbc58c88834e3f6f499094bad Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Thu, 3 Jun 2021 17:53:18 -0700 Subject: [PATCH 198/224] Update src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs Co-authored-by: Andrew Hall --- .../Runtime/ForwardCancellationTokenToInvocations.Fixer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index 3928454caf..5360f9b337 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -126,7 +126,7 @@ private static SyntaxNode TryGenerateNewDocumentRoot( ImmutableArray newArguments; if (paramsArrayType is not null) { - // current callsite is a params array, we need to wrap all these arguments to preserve sematnics + // current callsite is a params array, we need to wrap all these arguments to preserve semantics var typeSyntax = GetTypeSyntaxForArray(paramsArrayType); var expressions = GetExpressions(currentArguments); newArguments = ImmutableArray.Create(GetArrayCreationExpression(generator, typeSyntax, expressions)); @@ -165,4 +165,4 @@ public MyCodeAction(string title, Func> create } } } -} \ No newline at end of file +} From 4fe999ddd596018739c7f353851962998db9657d Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Fri, 16 Jul 2021 20:24:21 -0700 Subject: [PATCH 199/224] fixing formatting --- ...wardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb | 4 +--- temp/perfBaseline | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) create mode 160000 temp/perfBaseline diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb index 1e6787ea70..72e157df68 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb @@ -106,9 +106,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return syntax End If - If symbol.IsTupleType AndAlso - symbol.TupleUnderlyingType IsNot Nothing AndAlso - Not symbol.Equals(symbol.TupleUnderlyingType) Then + If symbol.IsTupleType AndAlso symbol.TupleUnderlyingType IsNot Nothing AndAlso Not symbol.Equals(symbol.TupleUnderlyingType) Then Return CreateSimpleTypeSyntax(symbol.TupleUnderlyingType) End If diff --git a/temp/perfBaseline b/temp/perfBaseline new file mode 160000 index 0000000000..f71538a093 --- /dev/null +++ b/temp/perfBaseline @@ -0,0 +1 @@ +Subproject commit f71538a0935809fdc9b6dd17046f86809a231109 From 23ae55345e0b0fcb9ffb3b966b5f87597034f29b Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 2 Aug 2021 10:34:12 -0700 Subject: [PATCH 200/224] update headers --- ...orwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs | 2 +- src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs | 2 +- ...orwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs index 371446acce..cb4f5f58d5 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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.Diagnostics.CodeAnalysis; diff --git a/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs b/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs index b3baea62a0..233729f0ce 100644 --- a/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs +++ b/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Reflection; using Microsoft.CodeAnalysis; diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb index 72e157df68..4d85e7d043 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb @@ -1,4 +1,4 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Simplification From 77cb72aab59279f8424024337e9b620dcd18380d Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 2 Aug 2021 10:34:36 -0700 Subject: [PATCH 201/224] use preview language version --- src/PerformanceTests/Tests/PerformanceTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PerformanceTests/Tests/PerformanceTests.csproj b/src/PerformanceTests/Tests/PerformanceTests.csproj index 5b32efdb20..c35c019b5a 100644 --- a/src/PerformanceTests/Tests/PerformanceTests.csproj +++ b/src/PerformanceTests/Tests/PerformanceTests.csproj @@ -1,7 +1,7 @@  net6.0 - 9.0 + preview disable Exe From abe363cbc1fc9bd199564ede81cfb5a67d6a9cea Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 2 Aug 2021 10:35:03 -0700 Subject: [PATCH 202/224] prefer `is not null` to `is object` --- src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs b/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs index 233729f0ce..4957e192df 100644 --- a/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs +++ b/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Reflection; using Microsoft.CodeAnalysis; @@ -13,7 +13,7 @@ internal static class NullableSyntaxAnnotationEx static NullableSyntaxAnnotationEx() { var nullableSyntaxAnnotation = typeof(Workspace).Assembly.GetType("Microsoft.CodeAnalysis.CodeGeneration.NullableSyntaxAnnotation", throwOnError: false); - if (nullableSyntaxAnnotation is object) + if (nullableSyntaxAnnotation is not null) { Oblivious = (SyntaxAnnotation?)nullableSyntaxAnnotation.GetField(nameof(Oblivious), BindingFlags.Static | BindingFlags.Public)?.GetValue(null); AnnotatedOrNotAnnotated = (SyntaxAnnotation?)nullableSyntaxAnnotation.GetField(nameof(AnnotatedOrNotAnnotated), BindingFlags.Static | BindingFlags.Public)?.GetValue(null); From b87a9f323c1197432b002506f9cff9df8a12ee9b Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Mon, 2 Aug 2021 10:35:45 -0700 Subject: [PATCH 203/224] fixup rebase --- ...orwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs | 2 +- .../Runtime/ForwardCancellationTokenToInvocations.Fixer.cs | 2 +- ...orwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs index cb4f5f58d5..a5d74c0759 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +// 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.Diagnostics.CodeAnalysis; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index 5360f9b337..5c0b72d9f6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -111,7 +111,7 @@ Task CreateChangedDocumentAsync(CancellationToken _) context.Diagnostics); } - private static SyntaxNode TryGenerateNewDocumentRoot( + private SyntaxNode TryGenerateNewDocumentRoot( Document doc, SyntaxNode root, IInvocationOperation invocation, diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb index 4d85e7d043..d26b5257bb 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb @@ -1,4 +1,4 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Simplification From 0947751bc28416fcdf8e9a761f37e78d761ec581 Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Tue, 3 Aug 2021 11:11:47 -0400 Subject: [PATCH 204/224] Update license headers in analyzer and tests --- .../Runtime/AvoidConstArrays.Fixer.cs | 2 +- .../Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs | 3 ++- .../Runtime/AvoidConstArraysTests.Fixer.cs | 2 +- .../Runtime/AvoidConstArraysTests.cs | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index f53480e88b..d915a20b67 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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.Immutable; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs index 8390b70890..a0f97438eb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; using Analyzer.Utilities; @@ -40,6 +40,7 @@ public override void Initialize(AnalysisContext context) context.RegisterOperationAction(operationContext => { var argument = (IArgumentOperation)operationContext.Operation; + if (argument.Value.Type.TypeKind != TypeKind.Array // Check that argument is an array || argument.Value.Kind != OperationKind.Literal // Must be literal array, which contains constant, literal values || argument.ArgumentKind != ArgumentKind.Explicit) // Must be explicitly declared diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.Fixer.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.Fixer.cs index c4b40a8568..2a5715cbf7 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.Fixer.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.Fixer.cs @@ -1 +1 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index a7e58295e7..93f2e6af1b 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; From e8f8f70bf452fb64970340963ec14fe699d5bbf6 Mon Sep 17 00:00:00 2001 From: Chris Rummel Date: Tue, 3 Aug 2021 08:54:46 -0500 Subject: [PATCH 205/224] Remove half-checked-in submodule. --- temp/perfBaseline | 1 - 1 file changed, 1 deletion(-) delete mode 160000 temp/perfBaseline diff --git a/temp/perfBaseline b/temp/perfBaseline deleted file mode 160000 index f71538a093..0000000000 --- a/temp/perfBaseline +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f71538a0935809fdc9b6dd17046f86809a231109 From 5eaffdab1fc7fb7b16f7632e46dc22ae910e78ed Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Tue, 3 Aug 2021 14:35:42 -0400 Subject: [PATCH 206/224] Added comments and more unit tests --- .../Runtime/AvoidConstArrays.cs | 3 + .../Runtime/AvoidConstArraysTests.cs | 148 +++++++++++++++++- 2 files changed, 146 insertions(+), 5 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs index a0f97438eb..9f55f47a60 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -32,11 +32,14 @@ public sealed class AvoidConstArraysAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + // Note that this analyzer doesn't analyze local variables that are only referenced once ever when passed as arguments, + // as cleaning up useless allocations is not in the scope of this analyzer public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + // Analyzes an argument operation context.RegisterOperationAction(operationContext => { var argument = (IArgumentOperation)operationContext.Operation; diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index 93f2e6af1b..72eae0f395 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -15,13 +15,29 @@ namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests { public class AvoidConstArraysTests { + #region C# Tests + [Fact] - public async Task IdentifyConstArrays_Explicit_Init() + public async Task CA1839CSharpIdentifyConstArrays() { + // Implicit initialization check + await VerifyCS.VerifyAnalyzerAsync(@" +using System; + +public class A +{ + public void B() + { + Console.WriteLine(new[]{ 1, 2, 3 }); + } +} +"); + + // Explicit initialization check await VerifyCS.VerifyAnalyzerAsync(@" using System; -class A +public class A { public void B() { @@ -32,19 +48,141 @@ public void B() } [Fact] - public async Task IdentifyConstArrays_Implicit_Init() + public async Task CA1839CSharpIgnoreOtherArgs() { + // A string await VerifyCS.VerifyAnalyzerAsync(@" using System; -class A +public class A { public void B() { - Console.WriteLine(new[]{ 1, 2, 3 }); + Console.WriteLine(""Lorem ipsum""); + } +} +"); + + // Test another type to be extra safe + await VerifyCS.VerifyAnalyzerAsync(@" +using System; + +public class A +{ + public void B() + { + Console.WriteLine(123); + } +} +"); + + // Non-literal array + await VerifyCS.VerifyAnalyzerAsync(@" +using System; + +public class A +{ + public void B() + { + string str = ""Lorem ipsum""; + Console.WriteLine(str.Split(' ')); + } +} +"); + + // Nested arguments + await VerifyCS.VerifyAnalyzerAsync(@" +using System; + +public class A +{ + public void B() + { + Console.WriteLine(string.Join(' ', new[] { ""Cake"", ""is"", ""good"" })); } } "); } + + #endregion + + #region Visual Basic Tests + + [Fact] + public async Task CA1839VisualBasicIdentifyConstArrays() + { + // Implicit initialization check + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Public Class A + Public Sub B() + Console.WriteLine({1, 2, 3}) + End Sub +End Class +"); + + // Explicit initialization check + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Public Class A + Public Sub B() + Console.WriteLine(New Integer() {1, 2, 3}) + End Sub +End Class +"); + } + + [Fact] + public async Task CA1839VisualBasicIgnoreOtherArgs() + { + // A string + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Public Class A + Public Sub B() + Console.WriteLine(""Lorem ipsum"") + End Sub +End Class +"); + + // Test another type to be extra safe + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Public Class A + Public Sub B() + Console.WriteLine(123) + End Sub +End Class +"); + + // Non-literal array + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Public Class A + Public Sub B() + Dim str As String = ""Lorem ipsum"" + Console.WriteLine(str.Split("" ""c)) + End Sub +End Class +"); + + // Nested arguments + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Public Class A + Public Sub B() + Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) + End Sub +End Class +"); + } + + #endregion } } \ No newline at end of file From 9b36c185f10aec72c61627c09801c522f9616510 Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Tue, 3 Aug 2021 17:51:40 -0400 Subject: [PATCH 207/224] Update csharp unit tests for analyzer --- .../Runtime/AvoidConstArrays.cs | 3 ++- .../Runtime/AvoidConstArraysTests.cs | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs index 9f55f47a60..2965b26dc7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -45,12 +45,13 @@ public override void Initialize(AnalysisContext context) var argument = (IArgumentOperation)operationContext.Operation; if (argument.Value.Type.TypeKind != TypeKind.Array // Check that argument is an array - || argument.Value.Kind != OperationKind.Literal // Must be literal array, which contains constant, literal values + || argument.Value.Kind != OperationKind.Literal // Must be literal array || argument.ArgumentKind != ArgumentKind.Explicit) // Must be explicitly declared { return; } + // Report diagnostic from argument context rather than argument.Value context operationContext.ReportDiagnostic(argument.CreateDiagnostic(Rule)); }, OperationKind.Argument); } diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index 72eae0f395..46bb509e2a 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -18,7 +18,7 @@ public class AvoidConstArraysTests #region C# Tests [Fact] - public async Task CA1839CSharpIdentifyConstArrays() + public async Task CA1839_CSharp_IdentifyConstArrays() { // Implicit initialization check await VerifyCS.VerifyAnalyzerAsync(@" @@ -48,7 +48,7 @@ public void B() } [Fact] - public async Task CA1839CSharpIgnoreOtherArgs() + public async Task CA1839_CSharp_NoDiagnostic_IgnoreOtherArgs() { // A string await VerifyCS.VerifyAnalyzerAsync(@" @@ -98,7 +98,7 @@ public class A { public void B() { - Console.WriteLine(string.Join(' ', new[] { ""Cake"", ""is"", ""good"" })); + Console.WriteLine("" "".Join(new[] { ""Cake"", ""is"", ""good"" })); } } "); @@ -109,7 +109,7 @@ public void B() #region Visual Basic Tests [Fact] - public async Task CA1839VisualBasicIdentifyConstArrays() + public async Task CA1839_VisualBasic_IdentifyConstArrays() { // Implicit initialization check await VerifyVB.VerifyAnalyzerAsync(@" @@ -135,7 +135,7 @@ End Class } [Fact] - public async Task CA1839VisualBasicIgnoreOtherArgs() + public async Task CA1839_VisualBasic_NoDiagnostic_IgnoreOtherArgs() { // A string await VerifyVB.VerifyAnalyzerAsync(@" From 790f06bd3d58c587ca2e7e26e93dd0d1efece626 Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Wed, 4 Aug 2021 18:07:21 -0400 Subject: [PATCH 208/224] Drafted code fixer and added unit tests --- .../Runtime/AvoidConstArrays.Fixer.cs | 84 +++++++--- .../Runtime/AvoidConstArrays.cs | 5 +- .../Runtime/AvoidConstArraysTests.Fixer.cs | 1 - .../Runtime/AvoidConstArraysTests.cs | 149 ++++++++++++++++-- 4 files changed, 206 insertions(+), 33 deletions(-) delete mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.Fixer.cs diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index d915a20b67..823c690707 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -1,6 +1,8 @@ // 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.Linq; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Threading; @@ -21,26 +23,70 @@ public sealed class AvoidConstArraysFixer : CodeFixProvider public override async Task RegisterCodeFixesAsync(CodeFixContext context) { - Document document = context.Document; - SyntaxTree tree = await document.GetSyntaxTreeAsync(context.CancellationToken).ConfigureAwait(false); - - // Apply fix depending on how original constant was written - // Name of static field can be [type]Array - string title = MicrosoftNetCoreAnalyzersResources.AvoidConstArraysTitle; - context.RegisterCodeFix( - new MyCodeAction( - title, - async cancellationToken => - { - DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - // Make changes here - return editor.GetChangedDocument(); - }, - equivalenceKey: title - ), - context.Diagnostics + await Task.Run(() => + { + string title = MicrosoftNetCoreAnalyzersResources.AvoidConstArraysTitle; + context.RegisterCodeFix( + new MyCodeAction( + title, + c => ExtractConstArrayAsync(context.Document, context.Diagnostics, c), + equivalenceKey: title), + context.Diagnostics); + }).ConfigureAwait(false); + } + + private static async Task ExtractConstArrayAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken) + { + SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SemanticModel model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // The containing method accessibility and the called method accessibility + // should both be considered when setting the new member's accessibility + var memberGroup = model.GetMemberGroup(root, cancellationToken).FirstOrDefault(); + + string newMemberName = GetExtractedMemberName( + memberGroup.ContainingType.MemberNames, + diagnostics.First().Properties["matchingParameter"] ); - await Task.Run(() => { }).ConfigureAwait(false); + + DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + SyntaxGenerator generator = SyntaxGenerator.GetGenerator(document); + + SyntaxNode newMember = generator.WithName(root, newMemberName); + newMember = generator.WithModifiers(newMember, DeclarationModifiers.Static | DeclarationModifiers.ReadOnly); + newMember = generator.WithAccessibility(newMember, memberGroup.DeclaredAccessibility); // same as the method accessibility + + SyntaxNode lastFieldSyntaxNode = await generator.GetMembers(root).Where(x => x is IFieldSymbol) + .Select(x => (IFieldSymbol)x).Last().DeclaringSyntaxReferences.First().GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + editor.AddMember(lastFieldSyntaxNode, newMember); + + // Replace argument with a reference to our new member + editor.ReplaceNode(root, generator.Argument(newMember)); + + // Return changed document + return editor.GetChangedDocument(); + } + + // The called method's parameter names won't need to be checked in the case that both + // methods are in the same type as conflicts in paramter names and field names can be + // resolved by directly referencing the static readonly field everywhere it's needed + private static string GetExtractedMemberName(IEnumerable memberNames, string parameterName) + { + string nameOption = parameterName; + + if (memberNames.Contains(nameOption)) + { + nameOption += "Array"; + } + + int suffix = 1; + while (memberNames.Contains(nameOption)) + { + nameOption += suffix; + suffix++; + } + + return nameOption; } // Needed for Telemetry (https://github.com/dotnet/roslyn-analyzers/issues/192) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs index 2965b26dc7..80a4ebb603 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -51,8 +51,11 @@ public override void Initialize(AnalysisContext context) return; } + ImmutableDictionary properties = ImmutableDictionary.Create(); + properties.Add("matchingParameter", argument.Parameter.Name); + // Report diagnostic from argument context rather than argument.Value context - operationContext.ReportDiagnostic(argument.CreateDiagnostic(Rule)); + operationContext.ReportDiagnostic(argument.CreateDiagnostic(Rule, properties)); }, OperationKind.Argument); } } diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.Fixer.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.Fixer.cs deleted file mode 100644 index 2a5715cbf7..0000000000 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.Fixer.cs +++ /dev/null @@ -1 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index 46bb509e2a..3e7129f6b6 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -13,6 +13,9 @@ namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests { + // Analyzer and code fix tests are both in this file + // "Do not separate analyzer tests from code fix tests" + // https://github.com/dotnet/roslyn-analyzers/blob/main/docs/NetCore_GettingStarted.md#definition-of-done public class AvoidConstArraysTests { #region C# Tests @@ -21,7 +24,7 @@ public class AvoidConstArraysTests public async Task CA1839_CSharp_IdentifyConstArrays() { // Implicit initialization check - await VerifyCS.VerifyAnalyzerAsync(@" + await VerifyCS.VerifyCodeFixAsync(@" using System; public class A @@ -31,10 +34,22 @@ public void B() Console.WriteLine(new[]{ 1, 2, 3 }); } } +", @" +using System; + +public class A +{ + private static readonly int[] valueArray = new[]{ 1, 2, 3 }; + + public void B() + { + Console.WriteLine(valueArray); + } +} "); // Explicit initialization check - await VerifyCS.VerifyAnalyzerAsync(@" + await VerifyCS.VerifyCodeFixAsync(@" using System; public class A @@ -44,14 +59,38 @@ public void B() Console.WriteLine(new int[]{ 1, 2, 3 }); } } +", @" +using System; + +public class A +{ + private static readonly int[] valueArray = new int[]{ 1, 2, 3 }; + + public void B() + { + Console.WriteLine(valueArray); + } +} "); } [Fact] public async Task CA1839_CSharp_NoDiagnostic_IgnoreOtherArgs() { + // All code fix tests in this method result in no changes, as the analyzer will ignore these + // A string - await VerifyCS.VerifyAnalyzerAsync(@" + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +public class A +{ + public void B() + { + Console.WriteLine(""Lorem ipsum""); + } +} +", @" using System; public class A @@ -64,7 +103,17 @@ public void B() "); // Test another type to be extra safe - await VerifyCS.VerifyAnalyzerAsync(@" + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +public class A +{ + public void B() + { + Console.WriteLine(123); + } +} +", @" using System; public class A @@ -77,7 +126,18 @@ public void B() "); // Non-literal array - await VerifyCS.VerifyAnalyzerAsync(@" + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +public class A +{ + public void B() + { + string str = ""Lorem ipsum""; + Console.WriteLine(str.Split(' ')); + } +} +", @" using System; public class A @@ -91,7 +151,17 @@ public void B() "); // Nested arguments - await VerifyCS.VerifyAnalyzerAsync(@" + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +public class A +{ + public void B() + { + Console.WriteLine("" "".Join(new[] { ""Cake"", ""is"", ""good"" })); + } +} +", @" using System; public class A @@ -112,7 +182,7 @@ public void B() public async Task CA1839_VisualBasic_IdentifyConstArrays() { // Implicit initialization check - await VerifyVB.VerifyAnalyzerAsync(@" + await VerifyVB.VerifyCodeFixAsync(@" Imports System Public Class A @@ -120,10 +190,20 @@ Public Sub B() Console.WriteLine({1, 2, 3}) End Sub End Class +", @" +Imports System + +Public Class A + Private Shared ReadOnly valueArray As Integer() = {1, 2, 3} + + Public Sub B() + Console.WriteLine(valueArray) + End Sub +End Class "); // Explicit initialization check - await VerifyVB.VerifyAnalyzerAsync(@" + await VerifyVB.VerifyCodeFixAsync(@" Imports System Public Class A @@ -131,14 +211,34 @@ Public Sub B() Console.WriteLine(New Integer() {1, 2, 3}) End Sub End Class +", @" +Imports System + +Public Class A + Private Shared ReadOnly valueArray As Integer() = New Integer() {1, 2, 3} + + Public Sub B() + Console.WriteLine(valueArray) + End Sub +End Class "); } [Fact] public async Task CA1839_VisualBasic_NoDiagnostic_IgnoreOtherArgs() { + // All code fix tests in this method result in no changes, as the analyzer will ignore these + // A string - await VerifyVB.VerifyAnalyzerAsync(@" + await VerifyVB.VerifyCodeFixAsync(@" +Imports System + +Public Class A + Public Sub B() + Console.WriteLine(""Lorem ipsum"") + End Sub +End Class +", @" Imports System Public Class A @@ -149,7 +249,15 @@ End Class "); // Test another type to be extra safe - await VerifyVB.VerifyAnalyzerAsync(@" + await VerifyVB.VerifyCodeFixAsync(@" +Imports System + +Public Class A + Public Sub B() + Console.WriteLine(123) + End Sub +End Class +", @" Imports System Public Class A @@ -160,7 +268,16 @@ End Class "); // Non-literal array - await VerifyVB.VerifyAnalyzerAsync(@" + await VerifyVB.VerifyCodeFixAsync(@" +Imports System + +Public Class A + Public Sub B() + Dim str As String = ""Lorem ipsum"" + Console.WriteLine(str.Split("" ""c)) + End Sub +End Class +", @" Imports System Public Class A @@ -172,7 +289,15 @@ End Class "); // Nested arguments - await VerifyVB.VerifyAnalyzerAsync(@" + await VerifyVB.VerifyCodeFixAsync(@" +Imports System + +Public Class A + Public Sub B() + Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) + End Sub +End Class +", @" Imports System Public Class A From c3a4cffe189c3ac07e0a91947d8b2eea855ded91 Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Thu, 5 Aug 2021 09:17:11 -0400 Subject: [PATCH 209/224] Update VerifyCS and VerifyVB aliases to include the fixer class --- .../Runtime/AvoidConstArrays.Fixer.cs | 3 +-- .../Runtime/AvoidConstArraysTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index 823c690707..4077769136 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -40,8 +40,6 @@ private static async Task ExtractConstArrayAsync(Document document, Im SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SemanticModel model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - // The containing method accessibility and the called method accessibility - // should both be considered when setting the new member's accessibility var memberGroup = model.GetMemberGroup(root, cancellationToken).FirstOrDefault(); string newMemberName = GetExtractedMemberName( @@ -72,6 +70,7 @@ private static async Task ExtractConstArrayAsync(Document document, Im // resolved by directly referencing the static readonly field everywhere it's needed private static string GetExtractedMemberName(IEnumerable memberNames, string parameterName) { + // Half-shot attempt at getting a unique field name string nameOption = parameterName; if (memberNames.Contains(nameOption)) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index 3e7129f6b6..33898e87b5 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -6,10 +6,10 @@ using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysAnalyzer, - Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysFixer>; using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysAnalyzer, - Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysFixer>; namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests { From 0ef4becc1a0531a5f2591f8110e7763bca63f30b Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Thu, 5 Aug 2021 09:41:23 -0400 Subject: [PATCH 210/224] Update code fix tests --- .../Runtime/AvoidConstArraysTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index 33898e87b5..2e2ecbf8bb 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -158,7 +158,7 @@ public class A { public void B() { - Console.WriteLine("" "".Join(new[] { ""Cake"", ""is"", ""good"" })); + Console.WriteLine(string.Join("" "", new[] { ""Cake"", ""is"", ""good"" })); } } ", @" @@ -168,7 +168,7 @@ public class A { public void B() { - Console.WriteLine("" "".Join(new[] { ""Cake"", ""is"", ""good"" })); + Console.WriteLine(string.Join("" "", new[] { ""Cake"", ""is"", ""good"" })); } } "); @@ -294,7 +294,7 @@ Imports System Public Class A Public Sub B() - Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) + Console.WriteLine(Join("" ""c, {""Cake"", ""is"", ""good""})) End Sub End Class ", @" @@ -302,7 +302,7 @@ Imports System Public Class A Public Sub B() - Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) + Console.WriteLine(Join("" ""c, {""Cake"", ""is"", ""good""})) End Sub End Class "); From d6eb2f7084be8558376644b0de7568eaad5d862e Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Thu, 5 Aug 2021 09:55:01 -0400 Subject: [PATCH 211/224] Update VB test code --- .../Runtime/AvoidConstArraysTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index 2e2ecbf8bb..1e56967e9c 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -294,7 +294,7 @@ Imports System Public Class A Public Sub B() - Console.WriteLine(Join("" ""c, {""Cake"", ""is"", ""good""})) + Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) End Sub End Class ", @" @@ -302,7 +302,7 @@ Imports System Public Class A Public Sub B() - Console.WriteLine(Join("" ""c, {""Cake"", ""is"", ""good""})) + Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) End Sub End Class "); From 73d989156fa0eb2c9172e272056c9f032ed4244c Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Fri, 6 Aug 2021 15:39:38 -0400 Subject: [PATCH 212/224] Clean up extract method --- .../Runtime/AvoidConstArrays.Fixer.cs | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index 4077769136..0e87319561 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -39,23 +39,23 @@ private static async Task ExtractConstArrayAsync(Document document, Im { SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SemanticModel model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - - var memberGroup = model.GetMemberGroup(root, cancellationToken).FirstOrDefault(); - - string newMemberName = GetExtractedMemberName( - memberGroup.ContainingType.MemberNames, - diagnostics.First().Properties["matchingParameter"] - ); - DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); SyntaxGenerator generator = SyntaxGenerator.GetGenerator(document); + // Get method containing the symbol that is being diagnosed. Should always be in a method + IMethodSymbol containingMethod = GetContainingMethod(model.GetSymbolInfo(root, cancellationToken).Symbol)!; + + // Get a valid member name for the extracted constant + IEnumerable memberNames = model.GetTypeInfo(root, cancellationToken).Type.GetMembers().Where(x => x is IFieldSymbol).Select(x => x.Name); + string newMemberName = GetExtractedMemberName(memberNames, diagnostics.First().Properties["matchingParameter"]); + + // Create the new member SyntaxNode newMember = generator.WithName(root, newMemberName); newMember = generator.WithModifiers(newMember, DeclarationModifiers.Static | DeclarationModifiers.ReadOnly); - newMember = generator.WithAccessibility(newMember, memberGroup.DeclaredAccessibility); // same as the method accessibility + newMember = generator.WithAccessibility(newMember, containingMethod.DeclaredAccessibility); // same as the method accessibility - SyntaxNode lastFieldSyntaxNode = await generator.GetMembers(root).Where(x => x is IFieldSymbol) - .Select(x => (IFieldSymbol)x).Last().DeclaringSyntaxReferences.First().GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + // Add the new member to the end of the class fields + SyntaxNode lastFieldSyntaxNode = generator.GetMembers(root).Last(x => model.GetSymbolInfo(x).Symbol is IFieldSymbol); editor.AddMember(lastFieldSyntaxNode, newMember); // Replace argument with a reference to our new member @@ -65,6 +65,20 @@ private static async Task ExtractConstArrayAsync(Document document, Im return editor.GetChangedDocument(); } + private static IMethodSymbol? GetContainingMethod(ISymbol symbol) + { + ISymbol current = symbol; + while (current != null) + { + if (current is IMethodSymbol method) + { + return method; + } + current = current.ContainingSymbol; + } + return default; + } + // The called method's parameter names won't need to be checked in the case that both // methods are in the same type as conflicts in paramter names and field names can be // resolved by directly referencing the static readonly field everywhere it's needed From a19dc91d520f82cf0521ed8e306062c72ff5960f Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Fri, 6 Aug 2021 15:44:03 -0400 Subject: [PATCH 213/224] Refactored ISymbol.GetContainingMethod as an extension method --- .../Runtime/AvoidConstArrays.Fixer.cs | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index 0e87319561..b9e588ba89 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -43,9 +43,9 @@ private static async Task ExtractConstArrayAsync(Document document, Im SyntaxGenerator generator = SyntaxGenerator.GetGenerator(document); // Get method containing the symbol that is being diagnosed. Should always be in a method - IMethodSymbol containingMethod = GetContainingMethod(model.GetSymbolInfo(root, cancellationToken).Symbol)!; + IMethodSymbol containingMethod = model.GetSymbolInfo(root, cancellationToken).Symbol.GetContainingMethod()!; - // Get a valid member name for the extracted constant + // Get a valid member name for the extracted constant IEnumerable memberNames = model.GetTypeInfo(root, cancellationToken).Type.GetMembers().Where(x => x is IFieldSymbol).Select(x => x.Name); string newMemberName = GetExtractedMemberName(memberNames, diagnostics.First().Properties["matchingParameter"]); @@ -58,27 +58,13 @@ private static async Task ExtractConstArrayAsync(Document document, Im SyntaxNode lastFieldSyntaxNode = generator.GetMembers(root).Last(x => model.GetSymbolInfo(x).Symbol is IFieldSymbol); editor.AddMember(lastFieldSyntaxNode, newMember); - // Replace argument with a reference to our new member + // Replace argument with a reference to our new member editor.ReplaceNode(root, generator.Argument(newMember)); // Return changed document return editor.GetChangedDocument(); } - private static IMethodSymbol? GetContainingMethod(ISymbol symbol) - { - ISymbol current = symbol; - while (current != null) - { - if (current is IMethodSymbol method) - { - return method; - } - current = current.ContainingSymbol; - } - return default; - } - // The called method's parameter names won't need to be checked in the case that both // methods are in the same type as conflicts in paramter names and field names can be // resolved by directly referencing the static readonly field everywhere it's needed @@ -111,4 +97,21 @@ public MyCodeAction(string title, Func> create } } } + + public static class ISymbolExtensions + { + public static IMethodSymbol? GetContainingMethod(this ISymbol symbol) + { + ISymbol current = symbol; + while (current != null) + { + if (current is IMethodSymbol method) + { + return method; + } + current = current.ContainingSymbol; + } + return default; + } + } } \ No newline at end of file From e33170211668bd17456ca71019607fc1136d41d1 Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Fri, 6 Aug 2021 16:21:41 -0400 Subject: [PATCH 214/224] Refactored extraction call --- .../Runtime/AvoidConstArrays.Fixer.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index b9e588ba89..ef4b63bb22 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -23,19 +23,17 @@ public sealed class AvoidConstArraysFixer : CodeFixProvider public override async Task RegisterCodeFixesAsync(CodeFixContext context) { - await Task.Run(() => - { - string title = MicrosoftNetCoreAnalyzersResources.AvoidConstArraysTitle; - context.RegisterCodeFix( - new MyCodeAction( - title, - c => ExtractConstArrayAsync(context.Document, context.Diagnostics, c), - equivalenceKey: title), - context.Diagnostics); - }).ConfigureAwait(false); + string title = MicrosoftNetCoreAnalyzersResources.AvoidConstArraysTitle; + context.RegisterCodeFix( + new MyCodeAction( + title, + async c => await ExtractConstArrayAsync(context.Document, context.Diagnostics.First(), c).ConfigureAwait(false), + equivalenceKey: title), + context.Diagnostics); + await Task.Run(() => { }).ConfigureAwait(false); } - private static async Task ExtractConstArrayAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken) + private static async Task ExtractConstArrayAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SemanticModel model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -47,7 +45,7 @@ private static async Task ExtractConstArrayAsync(Document document, Im // Get a valid member name for the extracted constant IEnumerable memberNames = model.GetTypeInfo(root, cancellationToken).Type.GetMembers().Where(x => x is IFieldSymbol).Select(x => x.Name); - string newMemberName = GetExtractedMemberName(memberNames, diagnostics.First().Properties["matchingParameter"]); + string newMemberName = GetExtractedMemberName(memberNames, diagnostic.Properties["matchingParameter"]); // Create the new member SyntaxNode newMember = generator.WithName(root, newMemberName); From 5ff97191c9cc6db8f3919dd63bb38c14209c0051 Mon Sep 17 00:00:00 2001 From: kazukazuinaina Date: Thu, 5 Aug 2021 23:03:11 +0900 Subject: [PATCH 215/224] [refactor] test's location --- .../UnitTests/SymbolIsBannedAnalyzerTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs index ed3e345a29..905d9df02b 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs @@ -572,8 +572,8 @@ class C { void M() { - var c = new Banned(); - var d = new Banned(1); + var c = {|#0:new Banned()|}; + var d = {|#1:new Banned(1)|}; } } }"; @@ -584,12 +584,12 @@ void M() await VerifyCSharpAnalyzerAsync( source, bannedText1, - GetCSharpResultAt(13, 21, 33, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Banned.Banned()", "")); + GetCSharpResultAt(0, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Banned.Banned()", "")); await VerifyCSharpAnalyzerAsync( source, bannedText2, - GetCSharpResultAt(14, 21, 34, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Banned.Banned(int)", "")); + GetCSharpResultAt(1, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Banned.Banned(int)", "")); } [Fact] From af26d85c69d611c1f289742c469f627c058972e4 Mon Sep 17 00:00:00 2001 From: kazukazuinaina Date: Fri, 6 Aug 2021 00:19:21 +0900 Subject: [PATCH 216/224] [delete] Test's api GetCSharpResultAt(int line, int startColumn, int endColumn, DiagnosticDescriptor descriptor, string bannedMemberName, string message) GetBasicResultAt(int line, int startColumn, int endColumn, DiagnosticDescriptor descriptor, string bannedMemberName, string message) --- .../UnitTests/SymbolIsBannedAnalyzerTests.cs | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs index 905d9df02b..6a1fbcbe64 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs @@ -25,21 +25,11 @@ private static DiagnosticResult GetCSharpResultAt(int markupKey, DiagnosticDescr .WithLocation(markupKey) .WithArguments(bannedMemberName, message); - private static DiagnosticResult GetCSharpResultAt(int line, int startColumn, int endColumn, DiagnosticDescriptor descriptor, string bannedMemberName, string message) - => VerifyCS.Diagnostic(descriptor) - .WithSpan(line, startColumn, line, endColumn) - .WithArguments(bannedMemberName, message); - private static DiagnosticResult GetBasicResultAt(int markupKey, DiagnosticDescriptor descriptor, string bannedMemberName, string message) => VerifyVB.Diagnostic(descriptor) .WithLocation(markupKey) .WithArguments(bannedMemberName, message); - private static DiagnosticResult GetBasicResultAt(int line, int startColumn, int endColumn, DiagnosticDescriptor descriptor, string bannedMemberName, string message) - => VerifyVB.Diagnostic(descriptor) - .WithSpan(line, startColumn, line, endColumn) - .WithArguments(bannedMemberName, message); - private static async Task VerifyBasicAnalyzerAsync(string source, string bannedApiText, params DiagnosticResult[] expected) { var test = new VerifyVB.Test @@ -606,9 +596,9 @@ public void Banned(T t) {} void M() { - Banned(); - Banned(1); - Banned(""""); + {|#0:Banned()|}; + {|#1:Banned(1)|}; + {|#2:Banned("""")|}; } } @@ -620,9 +610,9 @@ public void Banned(U u) {} void M() { - Banned(); - Banned(1); - Banned(""""); + {|#3:Banned()|}; + {|#4:Banned(1)|}; + {|#5:Banned("""")|}; } } }"; @@ -637,32 +627,32 @@ void M() await VerifyCSharpAnalyzerAsync( source, bannedText1, - GetCSharpResultAt(12, 13, 21, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "C.Banned()", "")); + GetCSharpResultAt(0, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "C.Banned()", "")); await VerifyCSharpAnalyzerAsync( source, bannedText2, - GetCSharpResultAt(13, 13, 22, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "C.Banned(int)", "")); + GetCSharpResultAt(1, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "C.Banned(int)", "")); await VerifyCSharpAnalyzerAsync( source, bannedText3, - GetCSharpResultAt(14, 13, 31, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "C.Banned(T)", "")); + GetCSharpResultAt(2, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "C.Banned(T)", "")); await VerifyCSharpAnalyzerAsync( source, bannedText4, - GetCSharpResultAt(26, 13, 21, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "D.Banned()", "")); + GetCSharpResultAt(3, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "D.Banned()", "")); await VerifyCSharpAnalyzerAsync( source, bannedText5, - GetCSharpResultAt(27, 13, 22, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "D.Banned(int)", "")); + GetCSharpResultAt(4, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "D.Banned(int)", "")); await VerifyCSharpAnalyzerAsync( source, bannedText6, - GetCSharpResultAt(28, 13, 31, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "D.Banned(U)", "")); + GetCSharpResultAt(5, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "D.Banned(U)", "")); } [Fact] @@ -1307,8 +1297,8 @@ Sub New(ByVal I As Integer) : End Sub End Class Class C Sub M() - Dim c As New Banned() - Dim d As New Banned(1) + Dim c As {|#0:New Banned()|} + Dim d As {|#1:New Banned(1)|} End Sub End Class End Namespace"; @@ -1319,12 +1309,12 @@ End Class await VerifyBasicAnalyzerAsync( source, bannedText1, - GetBasicResultAt(9, 22, 34, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub New()", "")); + GetBasicResultAt(0,SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub New()", "")); await VerifyBasicAnalyzerAsync( source, bannedText2, - GetBasicResultAt(10, 22, 35, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub New(I As Integer)", "")); + GetBasicResultAt(1, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub New(I As Integer)", "")); } [Fact] @@ -1336,8 +1326,8 @@ Class C Sub Banned : End Sub Sub Banned(ByVal I As Integer) : End Sub Sub M() - Me.Banned() - Me.Banned(1) + {|#0:Me.Banned()|} + {|#1:Me.Banned(1)|} End Sub End Class End Namespace"; @@ -1348,12 +1338,12 @@ End Class await VerifyBasicAnalyzerAsync( source, bannedText1, - GetBasicResultAt(7, 13, 24, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub Banned()", "")); + GetBasicResultAt(0, ymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub Banned()", "")); await VerifyBasicAnalyzerAsync( source, bannedText2, - GetBasicResultAt(8, 13, 25, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub Banned(I As Integer)", "")); + GetBasicResultAt(1, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub Banned(I As Integer)", "")); } [Fact] From 38c924c2ebf88df201319804959ec2ca6836035d Mon Sep 17 00:00:00 2001 From: IK <36619465+kazukazuinaina@users.noreply.github.com> Date: Fri, 6 Aug 2021 00:36:26 +0900 Subject: [PATCH 217/224] Update src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs Co-authored-by: Sam Harwell --- .../UnitTests/SymbolIsBannedAnalyzerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs index 6a1fbcbe64..42a42da2a8 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs @@ -1338,7 +1338,7 @@ End Class await VerifyBasicAnalyzerAsync( source, bannedText1, - GetBasicResultAt(0, ymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub Banned()", "")); + GetBasicResultAt(0, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub Banned()", "")); await VerifyBasicAnalyzerAsync( source, From 8fd73a997711e78cc0d678536a9afdfe712c4c56 Mon Sep 17 00:00:00 2001 From: kazukazuinaina Date: Fri, 6 Aug 2021 01:12:14 +0900 Subject: [PATCH 218/224] [fix] code style --- .../UnitTests/SymbolIsBannedAnalyzerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs index 42a42da2a8..8042a41a19 100644 --- a/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs +++ b/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs @@ -1309,7 +1309,7 @@ End Class await VerifyBasicAnalyzerAsync( source, bannedText1, - GetBasicResultAt(0,SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub New()", "")); + GetBasicResultAt(0, SymbolIsBannedAnalyzer.SymbolIsBannedRule, "Public Sub New()", "")); await VerifyBasicAnalyzerAsync( source, From 2d2a8c0ec0451c4223a5e06c7cddb4cfacbe6ed0 Mon Sep 17 00:00:00 2001 From: steveberdy Date: Tue, 10 Aug 2021 03:28:30 +0000 Subject: [PATCH 219/224] Added operation kinds for operation registration --- .../Runtime/AvoidConstArrays.Fixer.cs | 10 ++- .../Runtime/AvoidConstArrays.cs | 66 +++++++++++++++---- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index ef4b63bb22..8cc4e7d90d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -11,9 +11,13 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; namespace Microsoft.NetCore.Analyzers.Runtime { + /// + /// CA1839: Avoid const arrays. Replace with static readonly arrays. + /// [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] public sealed class AvoidConstArraysFixer : CodeFixProvider { @@ -45,7 +49,11 @@ private static async Task ExtractConstArrayAsync(Document document, Di // Get a valid member name for the extracted constant IEnumerable memberNames = model.GetTypeInfo(root, cancellationToken).Type.GetMembers().Where(x => x is IFieldSymbol).Select(x => x.Name); - string newMemberName = GetExtractedMemberName(memberNames, diagnostic.Properties["matchingParameter"]); + if (!diagnostic.Properties.TryGetValue("matchingParamater", out string matchingParamater)) + { + matchingParamater = ((IArgumentOperation)model.GetOperation(root)).Parameter.Name; + } + string newMemberName = GetExtractedMemberName(memberNames, matchingParamater); // Create the new member SyntaxNode newMember = generator.WithName(root, newMemberName); diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs index 80a4ebb603..ca1bc3b6ed 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -11,7 +11,7 @@ namespace Microsoft.NetCore.Analyzers.Runtime { /// /// CA1839: Avoid const arrays. Replace with static readonly arrays. - /// + /// [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AvoidConstArraysAnalyzer : DiagnosticAnalyzer { @@ -32,7 +32,7 @@ public sealed class AvoidConstArraysAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - // Note that this analyzer doesn't analyze local variables that are only referenced once ever when passed as arguments, + // Note that this analyzer doesn't analyze local variables that are only referenced once ever, when passed as arguments, // as cleaning up useless allocations is not in the scope of this analyzer public override void Initialize(AnalysisContext context) { @@ -42,21 +42,63 @@ public override void Initialize(AnalysisContext context) // Analyzes an argument operation context.RegisterOperationAction(operationContext => { - var argument = (IArgumentOperation)operationContext.Operation; + if (operationContext.Operation is IArrayCreationOperation arrayCreationOperation + && arrayCreationOperation.GetAncestor(OperationKind.Argument) != null) + { + var test = "test"; + } - if (argument.Value.Type.TypeKind != TypeKind.Array // Check that argument is an array - || argument.Value.Kind != OperationKind.Literal // Must be literal array - || argument.ArgumentKind != ArgumentKind.Explicit) // Must be explicitly declared + if (operationContext.Operation is IArgumentOperation argumentOperation) { - return; + if (argumentOperation.Value.Type.TypeKind != TypeKind.Array // Check that argument is an array + || argumentOperation.Value.Kind != OperationKind.Literal // Must be literal array + || argumentOperation.ArgumentKind != ArgumentKind.Explicit) // Must be explicitly declared + { + return; + } + + ImmutableDictionary properties = ImmutableDictionary.Create(); + properties.Add("matchingParameter", argumentOperation.Parameter.Name); + + // Report diagnostic from argument context rather than argument.Value context + operationContext.ReportDiagnostic(argumentOperation.CreateDiagnostic(Rule, properties)); } + // else if (operationContext.Operation is IArrayCreationOperation arrayCreationOperation) + // { + // if (arrayCreationOperation.Kind != OperationKind.Literal) // Must be literal array + // { + // return; + // } + + // operationContext.ReportDiagnostic(arrayCreationOperation.CreateDiagnostic(Rule)); + // } + // else if (operationContext.Operation is IArrayInitializerOperation arrayInitializerOperation) + // { + // if (arrayInitializerOperation.Kind != OperationKind.Literal) // Must be literal array + // { + // return; + // } - ImmutableDictionary properties = ImmutableDictionary.Create(); - properties.Add("matchingParameter", argument.Parameter.Name); + // operationContext.ReportDiagnostic(arrayInitializerOperation.CreateDiagnostic(Rule)); + // } + // else if (operationContext.Operation is ILiteralOperation literalOperation) + // { + // if (literalOperation.Type.TypeKind != TypeKind.Array) // Must be literal array + // { + // return; + // } - // Report diagnostic from argument context rather than argument.Value context - operationContext.ReportDiagnostic(argument.CreateDiagnostic(Rule, properties)); - }, OperationKind.Argument); + // operationContext.ReportDiagnostic(literalOperation.CreateDiagnostic(Rule)); + // } + }, + OperationKind.Argument, + OperationKind.ArrayCreation, + OperationKind.ArrayInitializer, + OperationKind.Literal, + OperationKind.Invocation, + OperationKind.ExpressionStatement, // Hopefully not necessary + OperationKind.Block, // Hopefully not necessary + OperationKind.MethodBody); // Hopefully not necessary } } } \ No newline at end of file From 061417f406d70a0c5af44abb0141c329e75d3b4c Mon Sep 17 00:00:00 2001 From: steveberdy Date: Fri, 13 Aug 2021 00:12:50 +0000 Subject: [PATCH 220/224] Improved analyzer and code fixer --- .../MicrosoftNetCoreAnalyzersResources.resx | 2 +- .../Runtime/AvoidConstArrays.Fixer.cs | 91 +++++++++----- .../Runtime/AvoidConstArrays.cs | 66 +++------- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 4 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 4 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 4 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 4 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 4 +- .../Runtime/AvoidConstArraysTests.cs | 118 ++++++++++-------- 17 files changed, 170 insertions(+), 159 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 90f8405740..4efacd0c33 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -253,7 +253,7 @@ Having a constant array passed as an argument is not ideally performant. Extract constant arrays as 'static readonly'. - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields Test for empty strings using string length diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index 8cc4e7d90d..693b40a68a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -23,49 +23,72 @@ public sealed class AvoidConstArraysFixer : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(AvoidConstArraysAnalyzer.RuleId); + private static readonly string[] collectionMemberEndings = new[] { "array", "collection", "enumerable", "list" }; + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public override async Task RegisterCodeFixesAsync(CodeFixContext context) { + Document document = context.Document; + SyntaxNode root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode node = root.FindNode(context.Span); + SemanticModel model = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + DocumentEditor editor = await DocumentEditor.CreateAsync(document, context.CancellationToken).ConfigureAwait(false); + SyntaxGenerator generator = editor.Generator; string title = MicrosoftNetCoreAnalyzersResources.AvoidConstArraysTitle; + context.RegisterCodeFix( new MyCodeAction( title, - async c => await ExtractConstArrayAsync(context.Document, context.Diagnostics.First(), c).ConfigureAwait(false), + async c => await ExtractConstArrayAsync(root, node, model, editor, generator, context.Diagnostics.First(), c).ConfigureAwait(false), equivalenceKey: title), context.Diagnostics); - await Task.Run(() => { }).ConfigureAwait(false); } - private static async Task ExtractConstArrayAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + private static async Task ExtractConstArrayAsync(SyntaxNode root, SyntaxNode node, SemanticModel model, + DocumentEditor editor, SyntaxGenerator generator, Diagnostic diagnostic, CancellationToken cancellationToken) { - SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - SemanticModel model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - SyntaxGenerator generator = SyntaxGenerator.GetGenerator(document); + IOperation nodeOperation = model.GetOperation(node, cancellationToken); + INamedTypeSymbol containingType = model.GetEnclosingSymbol(node.SpanStart, cancellationToken).ContainingType; + IEnumerable typeMemberSymbols = containingType.GetMembers(); + IEnumerable typeMemberFields = typeMemberSymbols.Where(x => x is IFieldSymbol); // Get method containing the symbol that is being diagnosed. Should always be in a method - IMethodSymbol containingMethod = model.GetSymbolInfo(root, cancellationToken).Symbol.GetContainingMethod()!; + IMethodBodyOperation? containingMethod = nodeOperation.GetFirstAncestorOfType(); + ISymbol containingMethodSymbol = typeMemberSymbols.First(x => x.DeclaringSyntaxReferences.First().GetSyntax(cancellationToken) == containingMethod!.Syntax); + Accessibility newMemberAccessibility = containingMethodSymbol.DeclaredAccessibility; + // NOTE: need to get minimum viable accessibility // Get a valid member name for the extracted constant - IEnumerable memberNames = model.GetTypeInfo(root, cancellationToken).Type.GetMembers().Where(x => x is IFieldSymbol).Select(x => x.Name); - if (!diagnostic.Properties.TryGetValue("matchingParamater", out string matchingParamater)) - { - matchingParamater = ((IArgumentOperation)model.GetOperation(root)).Parameter.Name; - } - string newMemberName = GetExtractedMemberName(memberNames, matchingParamater); + IEnumerable memberNames = typeMemberFields.Select(x => x.Name); + string newMemberName = GetExtractedMemberName(memberNames, diagnostic.Properties["matchingParameter"]); // Create the new member - SyntaxNode newMember = generator.WithName(root, newMemberName); + SyntaxNode newMember = generator.WithName(node, newMemberName); newMember = generator.WithModifiers(newMember, DeclarationModifiers.Static | DeclarationModifiers.ReadOnly); - newMember = generator.WithAccessibility(newMember, containingMethod.DeclaredAccessibility); // same as the method accessibility + newMember = generator.WithAccessibility(newMember, newMemberAccessibility); // same as the method accessibility // Add the new member to the end of the class fields - SyntaxNode lastFieldSyntaxNode = generator.GetMembers(root).Last(x => model.GetSymbolInfo(x).Symbol is IFieldSymbol); - editor.AddMember(lastFieldSyntaxNode, newMember); + ISymbol lastFieldSymbol = typeMemberFields.LastOrDefault(); + if (lastFieldSymbol != null) + { + // Insert after last field if any fields are present + SyntaxNode lastFieldNode = await lastFieldSymbol.DeclaringSyntaxReferences.First().GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + editor.InsertAfter(lastFieldNode, newMember); + } + else + { + // Insert before first member if no fields are present + SyntaxNode firstMemberNode = (await containingType.DeclaringSyntaxReferences.First().GetSyntaxAsync(cancellationToken).ConfigureAwait(false)) + .ChildNodes().First(); + editor.InsertBefore(firstMemberNode, newMember); + } // Replace argument with a reference to our new member - editor.ReplaceNode(root, generator.Argument(newMember)); + //editor.ReplaceNode(node, generator.Argument(newMember)); + + // Replace root + editor.ReplaceNode(node, editor.GetChangedRoot()); // Return changed document return editor.GetChangedDocument(); @@ -76,10 +99,10 @@ private static async Task ExtractConstArrayAsync(Document document, Di // resolved by directly referencing the static readonly field everywhere it's needed private static string GetExtractedMemberName(IEnumerable memberNames, string parameterName) { - // Half-shot attempt at getting a unique field name string nameOption = parameterName; + bool hasCollectionEnding = collectionMemberEndings.Any(x => nameOption.EndsWith(x, true, System.Globalization.CultureInfo.InvariantCulture)); - if (memberNames.Contains(nameOption)) + if (memberNames.Contains(nameOption) && !hasCollectionEnding) { nameOption += "Array"; } @@ -104,18 +127,30 @@ public MyCodeAction(string title, Func> create } } - public static class ISymbolExtensions + public static class IOperationExtensions { - public static IMethodSymbol? GetContainingMethod(this ISymbol symbol) + public static T? GetFirstAncestorOfType(this IOperation operation, OperationKind kind) where T : IOperation + { + while (operation != null) + { + if (operation.Kind == kind) + { + return (T)operation; + } + operation = operation.Parent; + } + return default; + } + + public static T? GetFirstAncestorOfType(this IOperation operation) where T : IOperation { - ISymbol current = symbol; - while (current != null) + while (operation != null) { - if (current is IMethodSymbol method) + if (operation is T outOperation) { - return method; + return outOperation; } - current = current.ContainingSymbol; + operation = operation.Parent; } return default; } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs index ca1bc3b6ed..dd1ff8ce78 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +using System.Linq; +using System.Collections.Generic; using System.Collections.Immutable; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; @@ -42,63 +44,27 @@ public override void Initialize(AnalysisContext context) // Analyzes an argument operation context.RegisterOperationAction(operationContext => { - if (operationContext.Operation is IArrayCreationOperation arrayCreationOperation - && arrayCreationOperation.GetAncestor(OperationKind.Argument) != null) + if (operationContext.Operation is not IArrayCreationOperation arrayCreationOperation) { - var test = "test"; + return; } - if (operationContext.Operation is IArgumentOperation argumentOperation) - { - if (argumentOperation.Value.Type.TypeKind != TypeKind.Array // Check that argument is an array - || argumentOperation.Value.Kind != OperationKind.Literal // Must be literal array - || argumentOperation.ArgumentKind != ArgumentKind.Explicit) // Must be explicitly declared - { - return; - } - - ImmutableDictionary properties = ImmutableDictionary.Create(); - properties.Add("matchingParameter", argumentOperation.Parameter.Name); + IArgumentOperation? argumentOperation = arrayCreationOperation.GetAncestor(OperationKind.Argument); - // Report diagnostic from argument context rather than argument.Value context - operationContext.ReportDiagnostic(argumentOperation.CreateDiagnostic(Rule, properties)); + if (argumentOperation == null || // Must be literal array + !arrayCreationOperation.Children.First(x => x is IArrayInitializerOperation).Children.All(x => x is ILiteralOperation)) + { + return; } - // else if (operationContext.Operation is IArrayCreationOperation arrayCreationOperation) - // { - // if (arrayCreationOperation.Kind != OperationKind.Literal) // Must be literal array - // { - // return; - // } - // operationContext.ReportDiagnostic(arrayCreationOperation.CreateDiagnostic(Rule)); - // } - // else if (operationContext.Operation is IArrayInitializerOperation arrayInitializerOperation) - // { - // if (arrayInitializerOperation.Kind != OperationKind.Literal) // Must be literal array - // { - // return; - // } - - // operationContext.ReportDiagnostic(arrayInitializerOperation.CreateDiagnostic(Rule)); - // } - // else if (operationContext.Operation is ILiteralOperation literalOperation) - // { - // if (literalOperation.Type.TypeKind != TypeKind.Array) // Must be literal array - // { - // return; - // } + ImmutableDictionary properties = new Dictionary + { + { "matchingParameter", argumentOperation.Parameter.Name } + } + .ToImmutableDictionary(); - // operationContext.ReportDiagnostic(literalOperation.CreateDiagnostic(Rule)); - // } - }, - OperationKind.Argument, - OperationKind.ArrayCreation, - OperationKind.ArrayInitializer, - OperationKind.Literal, - OperationKind.Invocation, - OperationKind.ExpressionStatement, // Hopefully not necessary - OperationKind.Block, // Hopefully not necessary - OperationKind.MethodBody); // Hopefully not necessary + operationContext.ReportDiagnostic(arrayCreationOperation.CreateDiagnostic(Rule, properties)); + }, OperationKind.ArrayCreation); } } } \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index bdbe090b7f..6db43c5f9c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index bd644588a6..f6ba74945f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 960779608d..5a6ebb39dd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index ff4d8f8fa8..c82866275c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 0294c179b3..84a2312e9f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index a5d26bf349..a8ffdf8cd4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 5d8070348a..cfbfbdf519 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index e86cca93c5..83aeafe75f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 3e2d528739..2e20aa670d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 2ca57d2a73..2fc84d5fb5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 5a6b8f0b14..c263433820 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 3632db715a..de7f340897 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 0e7a09d986..6972b3d498 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -58,8 +58,8 @@ - Make any 'const' arrays 'static readonly' - Make any 'const' arrays 'static readonly' + Make any constant arrays passed as arguments into 'static readonly' fields + Make any constant arrays passed as arguments into 'static readonly' fields diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index 1e56967e9c..31ab66c8a4 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -34,16 +34,17 @@ public void B() Console.WriteLine(new[]{ 1, 2, 3 }); } } -", @" +", +VerifyCS.Diagnostic().WithSpan(8, 27, 8, 43), @" using System; public class A { - private static readonly int[] valueArray = new[]{ 1, 2, 3 }; + private static readonly int[] value = new[]{ 1, 2, 3 }; public void B() { - Console.WriteLine(valueArray); + Console.WriteLine(value); } } "); @@ -59,27 +60,22 @@ public void B() Console.WriteLine(new int[]{ 1, 2, 3 }); } } -", @" +", +VerifyCS.Diagnostic().WithSpan(8, 27, 8, 47), @" using System; public class A { - private static readonly int[] valueArray = new int[]{ 1, 2, 3 }; + private static readonly int[] value = new int[]{ 1, 2, 3 }; public void B() { - Console.WriteLine(valueArray); + Console.WriteLine(value); } } "); - } - - [Fact] - public async Task CA1839_CSharp_NoDiagnostic_IgnoreOtherArgs() - { - // All code fix tests in this method result in no changes, as the analyzer will ignore these - // A string + // Nested arguments await VerifyCS.VerifyCodeFixAsync(@" using System; @@ -87,22 +83,31 @@ public class A { public void B() { - Console.WriteLine(""Lorem ipsum""); + Console.WriteLine(string.Join("" "", new[] { ""Cake"", ""is"", ""good"" })); } } -", @" +", +VerifyCS.Diagnostic().WithSpan(8, 44, 8, 74), @" using System; public class A { + private static readonly string[] value = new[] { ""Cake"", ""is"", ""good"" }; + public void B() { - Console.WriteLine(""Lorem ipsum""); + Console.WriteLine(string.Join("" "", value)); } } "); + } - // Test another type to be extra safe + [Fact] + public async Task CA1839_CSharp_NoDiagnostic_IgnoreOtherArgs() + { + // All code fix tests in this method result in no changes, as the analyzer will ignore these + + // A string await VerifyCS.VerifyCodeFixAsync(@" using System; @@ -110,7 +115,7 @@ public class A { public void B() { - Console.WriteLine(123); + Console.WriteLine(""Lorem ipsum""); } } ", @" @@ -120,12 +125,12 @@ public class A { public void B() { - Console.WriteLine(123); + Console.WriteLine(""Lorem ipsum""); } } "); - // Non-literal array + // Test another type to be extra safe await VerifyCS.VerifyCodeFixAsync(@" using System; @@ -133,8 +138,7 @@ public class A { public void B() { - string str = ""Lorem ipsum""; - Console.WriteLine(str.Split(' ')); + Console.WriteLine(123); } } ", @" @@ -144,13 +148,12 @@ public class A { public void B() { - string str = ""Lorem ipsum""; - Console.WriteLine(str.Split(' ')); + Console.WriteLine(123); } } "); - // Nested arguments + // Non-literal array await VerifyCS.VerifyCodeFixAsync(@" using System; @@ -158,7 +161,8 @@ public class A { public void B() { - Console.WriteLine(string.Join("" "", new[] { ""Cake"", ""is"", ""good"" })); + string str = ""Lorem ipsum""; + Console.WriteLine(new[] { str }); } } ", @" @@ -168,7 +172,8 @@ public class A { public void B() { - Console.WriteLine(string.Join("" "", new[] { ""Cake"", ""is"", ""good"" })); + string str = ""Lorem ipsum""; + Console.WriteLine(new[] { str }); } } "); @@ -190,14 +195,15 @@ Public Sub B() Console.WriteLine({1, 2, 3}) End Sub End Class -", @" +", +VerifyVB.Diagnostic().WithSpan(6, 27, 6, 36), @" Imports System Public Class A - Private Shared ReadOnly valueArray As Integer() = {1, 2, 3} + Private Shared ReadOnly value As Integer() = {1, 2, 3} Public Sub B() - Console.WriteLine(valueArray) + Console.WriteLine(value) End Sub End Class "); @@ -211,50 +217,54 @@ Public Sub B() Console.WriteLine(New Integer() {1, 2, 3}) End Sub End Class -", @" +", +VerifyVB.Diagnostic().WithSpan(6, 27, 6, 50), @" Imports System Public Class A - Private Shared ReadOnly valueArray As Integer() = New Integer() {1, 2, 3} + Private Shared ReadOnly value As Integer() = New Integer() {1, 2, 3} Public Sub B() - Console.WriteLine(valueArray) + Console.WriteLine(value) End Sub End Class "); - } - - [Fact] - public async Task CA1839_VisualBasic_NoDiagnostic_IgnoreOtherArgs() - { - // All code fix tests in this method result in no changes, as the analyzer will ignore these - // A string + // Nested arguments await VerifyVB.VerifyCodeFixAsync(@" Imports System Public Class A Public Sub B() - Console.WriteLine(""Lorem ipsum"") + Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) End Sub End Class -", @" +", +VerifyVB.Diagnostic().WithSpan(6, 45, 6, 67), @" Imports System Public Class A + Private Shared ReadOnly value As String() = {""Cake"", ""is"", ""good""} + Public Sub B() - Console.WriteLine(""Lorem ipsum"") + Console.WriteLine(String.Join("" ""c, value)) End Sub End Class "); + } - // Test another type to be extra safe + [Fact] + public async Task CA1839_VisualBasic_NoDiagnostic_IgnoreOtherArgs() + { + // All code fix tests in this method result in no changes, as the analyzer will ignore these + + // A string await VerifyVB.VerifyCodeFixAsync(@" Imports System Public Class A Public Sub B() - Console.WriteLine(123) + Console.WriteLine(""Lorem ipsum"") End Sub End Class ", @" @@ -262,19 +272,18 @@ Imports System Public Class A Public Sub B() - Console.WriteLine(123) + Console.WriteLine(""Lorem ipsum"") End Sub End Class "); - // Non-literal array + // Test another type to be extra safe await VerifyVB.VerifyCodeFixAsync(@" Imports System Public Class A Public Sub B() - Dim str As String = ""Lorem ipsum"" - Console.WriteLine(str.Split("" ""c)) + Console.WriteLine(123) End Sub End Class ", @" @@ -282,19 +291,19 @@ Imports System Public Class A Public Sub B() - Dim str As String = ""Lorem ipsum"" - Console.WriteLine(str.Split("" ""c)) + Console.WriteLine(123) End Sub End Class "); - // Nested arguments + // Non-literal array await VerifyVB.VerifyCodeFixAsync(@" Imports System Public Class A Public Sub B() - Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) + Dim str As String = ""Lorem ipsum"" + Console.WriteLine({ str }) End Sub End Class ", @" @@ -302,7 +311,8 @@ Imports System Public Class A Public Sub B() - Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) + Dim str As String = ""Lorem ipsum"" + Console.WriteLine({ str }) End Sub End Class "); From 354228f6126869948cebe0e45fb5916c50759a48 Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Mon, 16 Aug 2021 15:22:18 +0000 Subject: [PATCH 221/224] Analyzer and code fix draft completed --- .../Runtime/AvoidConstArrays.Fixer.cs | 128 ++++++++++-------- .../Runtime/AvoidConstArrays.cs | 48 +++++-- .../Runtime/AvoidConstArraysTests.cs | 56 +++++--- 3 files changed, 142 insertions(+), 90 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index 693b40a68a..fdf15cb050 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -8,9 +8,11 @@ using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.NetCore.Analyzers.Runtime @@ -25,6 +27,7 @@ public sealed class AvoidConstArraysFixer : CodeFixProvider private static readonly string[] collectionMemberEndings = new[] { "array", "collection", "enumerable", "list" }; + // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public override async Task RegisterCodeFixesAsync(CodeFixContext context) @@ -40,37 +43,42 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix( new MyCodeAction( title, - async c => await ExtractConstArrayAsync(root, node, model, editor, generator, context.Diagnostics.First(), c).ConfigureAwait(false), + async c => await ExtractConstArrayAsync(node, model, editor, generator, context.Diagnostics.First().Properties, c).ConfigureAwait(false), equivalenceKey: title), context.Diagnostics); } - private static async Task ExtractConstArrayAsync(SyntaxNode root, SyntaxNode node, SemanticModel model, - DocumentEditor editor, SyntaxGenerator generator, Diagnostic diagnostic, CancellationToken cancellationToken) + private static async Task ExtractConstArrayAsync(SyntaxNode node, SemanticModel model, DocumentEditor editor, + SyntaxGenerator generator, ImmutableDictionary properties, CancellationToken cancellationToken) { - IOperation nodeOperation = model.GetOperation(node, cancellationToken); + IArrayCreationOperation arrayArgument = GetArrayCreationOperation(node, model, cancellationToken, out bool isInvoked); INamedTypeSymbol containingType = model.GetEnclosingSymbol(node.SpanStart, cancellationToken).ContainingType; - IEnumerable typeMemberSymbols = containingType.GetMembers(); - IEnumerable typeMemberFields = typeMemberSymbols.Where(x => x is IFieldSymbol); - - // Get method containing the symbol that is being diagnosed. Should always be in a method - IMethodBodyOperation? containingMethod = nodeOperation.GetFirstAncestorOfType(); - ISymbol containingMethodSymbol = typeMemberSymbols.First(x => x.DeclaringSyntaxReferences.First().GetSyntax(cancellationToken) == containingMethod!.Syntax); - Accessibility newMemberAccessibility = containingMethodSymbol.DeclaredAccessibility; - // NOTE: need to get minimum viable accessibility + IEnumerable typeFields = containingType.GetMembers().Where(x => x is IFieldSymbol); // Get a valid member name for the extracted constant - IEnumerable memberNames = typeMemberFields.Select(x => x.Name); - string newMemberName = GetExtractedMemberName(memberNames, diagnostic.Properties["matchingParameter"]); + IEnumerable memberNames = typeFields.Select(x => x.Name); + string newMemberName = GetExtractedMemberName(memberNames, properties["paramName"]); - // Create the new member - SyntaxNode newMember = generator.WithName(node, newMemberName); - newMember = generator.WithModifiers(newMember, DeclarationModifiers.Static | DeclarationModifiers.ReadOnly); - newMember = generator.WithAccessibility(newMember, newMemberAccessibility); // same as the method accessibility + // Get method containing the symbol that is being diagnosed. Should always be in a method + IOperation? containingMethodBody = arrayArgument.GetAncestor(OperationKind.MethodBody); + if (containingMethodBody is null) + { + // This is due to VB methods having a different structure than CS methods + containingMethodBody = arrayArgument.GetAncestor(OperationKind.Block); + } - // Add the new member to the end of the class fields - ISymbol lastFieldSymbol = typeMemberFields.LastOrDefault(); - if (lastFieldSymbol != null) + // Create the new member + SyntaxNode newMember = generator.FieldDeclaration( + newMemberName, + generator.TypeExpression(arrayArgument.Type), + GetAccessibility(model.GetEnclosingSymbol(containingMethodBody!.Syntax.SpanStart, cancellationToken)), + DeclarationModifiers.Static | DeclarationModifiers.ReadOnly, + arrayArgument.Syntax + ).FormatForExtraction(containingMethodBody.Syntax); + + // Add the new extracted member + ISymbol lastFieldSymbol = typeFields.LastOrDefault(); + if (lastFieldSymbol is not null) { // Insert after last field if any fields are present SyntaxNode lastFieldNode = await lastFieldSymbol.DeclaringSyntaxReferences.First().GetSyntaxAsync(cancellationToken).ConfigureAwait(false); @@ -78,31 +86,45 @@ private static async Task ExtractConstArrayAsync(SyntaxNode root, Synt } else { - // Insert before first member if no fields are present - SyntaxNode firstMemberNode = (await containingType.DeclaringSyntaxReferences.First().GetSyntaxAsync(cancellationToken).ConfigureAwait(false)) - .ChildNodes().First(); - editor.InsertBefore(firstMemberNode, newMember); + // Insert before first method if no fields are present, as a method already exists + editor.InsertBefore(containingMethodBody.Syntax, newMember); } // Replace argument with a reference to our new member - //editor.ReplaceNode(node, generator.Argument(newMember)); - - // Replace root - editor.ReplaceNode(node, editor.GetChangedRoot()); + SyntaxNode identifier = generator.IdentifierName(newMemberName); + if (isInvoked) + { + editor.ReplaceNode(node, generator.WithExpression(identifier, node)); + } + else + { + editor.ReplaceNode(node, generator.Argument(identifier)); + } // Return changed document - return editor.GetChangedDocument(); + return await Formatter.FormatAsync(editor.GetChangedDocument(), cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private static IArrayCreationOperation GetArrayCreationOperation(SyntaxNode node, SemanticModel model, CancellationToken cancellationToken, + out bool isInvoked) + { + // If this is a LINQ invocation, the node is already an IArrayCreationOperation + if (model.GetOperation(node, cancellationToken) is IArrayCreationOperation arrayCreation) + { + isInvoked = true; + return arrayCreation; + } + isInvoked = false; + return (IArrayCreationOperation)model.GetOperation(node.ChildNodes().First(), cancellationToken); } - // The called method's parameter names won't need to be checked in the case that both - // methods are in the same type as conflicts in paramter names and field names can be - // resolved by directly referencing the static readonly field everywhere it's needed private static string GetExtractedMemberName(IEnumerable memberNames, string parameterName) { string nameOption = parameterName; bool hasCollectionEnding = collectionMemberEndings.Any(x => nameOption.EndsWith(x, true, System.Globalization.CultureInfo.InvariantCulture)); - if (memberNames.Contains(nameOption) && !hasCollectionEnding) + if (parameterName == "source" // for LINQ, "sourceArray" is clearer than "source" + || (memberNames.Contains(nameOption) && !hasCollectionEnding)) { nameOption += "Array"; } @@ -117,6 +139,17 @@ private static string GetExtractedMemberName(IEnumerable memberNames, st return nameOption; } + private static Accessibility GetAccessibility(ISymbol originMethodSymbol) + { + if (Enum.TryParse(originMethodSymbol.GetResultantVisibility().ToString(), out Accessibility accessibility)) + { + return accessibility == Accessibility.Public + ? Accessibility.Private // public accessibility not wanted for fields + : accessibility; + } + return Accessibility.Private; + } + // Needed for Telemetry (https://github.com/dotnet/roslyn-analyzers/issues/192) private sealed class MyCodeAction : DocumentChangeAction { @@ -127,32 +160,11 @@ public MyCodeAction(string title, Func> create } } - public static class IOperationExtensions + internal static class SyntaxNodeExtensions { - public static T? GetFirstAncestorOfType(this IOperation operation, OperationKind kind) where T : IOperation + internal static SyntaxNode FormatForExtraction(this SyntaxNode node, SyntaxNode previouslyContainingNode) { - while (operation != null) - { - if (operation.Kind == kind) - { - return (T)operation; - } - operation = operation.Parent; - } - return default; - } - - public static T? GetFirstAncestorOfType(this IOperation operation) where T : IOperation - { - while (operation != null) - { - if (operation is T outOperation) - { - return outOperation; - } - operation = operation.Parent; - } - return default; + return node.HasTrailingTrivia ? node : node.WithTrailingTrivia(previouslyContainingNode.GetTrailingTrivia()); } } } \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs index dd1ff8ce78..f741f621ea 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -34,8 +34,6 @@ public sealed class AvoidConstArraysAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - // Note that this analyzer doesn't analyze local variables that are only referenced once ever, when passed as arguments, - // as cleaning up useless allocations is not in the scope of this analyzer public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); @@ -44,27 +42,51 @@ public override void Initialize(AnalysisContext context) // Analyzes an argument operation context.RegisterOperationAction(operationContext => { - if (operationContext.Operation is not IArrayCreationOperation arrayCreationOperation) + IArgumentOperation? argumentOperation; + Dictionary properties = new(); + + if (operationContext.Operation is IArrayCreationOperation arrayCreationOperation) // For arrays passed as arguments { - return; + argumentOperation = arrayCreationOperation.GetAncestor(OperationKind.Argument); + if (argumentOperation is null) + { + return; + } } + else if (operationContext.Operation is IInvocationOperation invocationOperation) // For arrays passed in extension methods, like in LINQ + { + if (invocationOperation.Descendants().Any(x => x is IArrayCreationOperation)) + { + // This is an invocation that contains an array in it + // This will get caught by the first case in another cycle + return; + } - IArgumentOperation? argumentOperation = arrayCreationOperation.GetAncestor(OperationKind.Argument); - - if (argumentOperation == null || // Must be literal array - !arrayCreationOperation.Children.First(x => x is IArrayInitializerOperation).Children.All(x => x is ILiteralOperation)) + argumentOperation = invocationOperation.Arguments.FirstOrDefault(); + if (argumentOperation is null || argumentOperation.Children.First() is not IConversionOperation conversionOperation + || conversionOperation.Operand is not IArrayCreationOperation) + { + return; + } + arrayCreationOperation = (IArrayCreationOperation)conversionOperation.Operand; + } + else { return; } - ImmutableDictionary properties = new Dictionary + // Must be literal array + if (!arrayCreationOperation.Children.First(x => x is IArrayInitializerOperation).Children.All(x => x is ILiteralOperation)) { - { "matchingParameter", argumentOperation.Parameter.Name } + return; } - .ToImmutableDictionary(); - operationContext.ReportDiagnostic(arrayCreationOperation.CreateDiagnostic(Rule, properties)); - }, OperationKind.ArrayCreation); + properties["paramName"] = argumentOperation.Parameter.Name; + + operationContext.ReportDiagnostic(arrayCreationOperation.CreateDiagnostic(Rule, properties.ToImmutableDictionary())); + }, + OperationKind.ArrayCreation, + OperationKind.Invocation); } } } \ No newline at end of file diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs index 31ab66c8a4..ed90f283fb 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Testing; -using Test.Utilities; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysAnalyzer, @@ -34,13 +32,12 @@ public void B() Console.WriteLine(new[]{ 1, 2, 3 }); } } -", -VerifyCS.Diagnostic().WithSpan(8, 27, 8, 43), @" +", VerifyCS.Diagnostic().WithSpan(8, 27, 8, 43), @" using System; public class A { - private static readonly int[] value = new[]{ 1, 2, 3 }; + private static readonly int[] value = new[] { 1, 2, 3 }; public void B() { @@ -60,13 +57,12 @@ public void B() Console.WriteLine(new int[]{ 1, 2, 3 }); } } -", -VerifyCS.Diagnostic().WithSpan(8, 27, 8, 47), @" +", VerifyCS.Diagnostic().WithSpan(8, 27, 8, 47), @" using System; public class A { - private static readonly int[] value = new int[]{ 1, 2, 3 }; + private static readonly int[] value = new int[] { 1, 2, 3 }; public void B() { @@ -86,8 +82,7 @@ public void B() Console.WriteLine(string.Join("" "", new[] { ""Cake"", ""is"", ""good"" })); } } -", -VerifyCS.Diagnostic().WithSpan(8, 44, 8, 74), @" +", VerifyCS.Diagnostic().WithSpan(8, 44, 8, 74), @" using System; public class A @@ -99,6 +94,35 @@ public void B() Console.WriteLine(string.Join("" "", value)); } } +"); + + // Extension method usage + await VerifyCS.VerifyCodeFixAsync(@" +using System; +using System.Linq; + +public class A +{ + public void B() + { + string y = new[] { ""a"", ""b"", ""c"" }.First(); + Console.WriteLine(y); + } +} +", VerifyCS.Diagnostic().WithSpan(9, 20, 9, 43), @" +using System; +using System.Linq; + +public class A +{ + private static readonly string[] sourceArray = new[] { ""a"", ""b"", ""c"" }; + + public void B() + { + string y = sourceArray.First(); + Console.WriteLine(y); + } +} "); } @@ -195,13 +219,11 @@ Public Sub B() Console.WriteLine({1, 2, 3}) End Sub End Class -", -VerifyVB.Diagnostic().WithSpan(6, 27, 6, 36), @" +", VerifyVB.Diagnostic().WithSpan(6, 27, 6, 36), @" Imports System Public Class A Private Shared ReadOnly value As Integer() = {1, 2, 3} - Public Sub B() Console.WriteLine(value) End Sub @@ -217,13 +239,11 @@ Public Sub B() Console.WriteLine(New Integer() {1, 2, 3}) End Sub End Class -", -VerifyVB.Diagnostic().WithSpan(6, 27, 6, 50), @" +", VerifyVB.Diagnostic().WithSpan(6, 27, 6, 50), @" Imports System Public Class A Private Shared ReadOnly value As Integer() = New Integer() {1, 2, 3} - Public Sub B() Console.WriteLine(value) End Sub @@ -239,13 +259,11 @@ Public Sub B() Console.WriteLine(String.Join("" ""c, {""Cake"", ""is"", ""good""})) End Sub End Class -", -VerifyVB.Diagnostic().WithSpan(6, 45, 6, 67), @" +", VerifyVB.Diagnostic().WithSpan(6, 45, 6, 67), @" Imports System Public Class A Private Shared ReadOnly value As String() = {""Cake"", ""is"", ""good""} - Public Sub B() Console.WriteLine(String.Join("" ""c, value)) End Sub From 5cf088467b80a35f19d776bbce1ed88a0daddf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 28 Jul 2021 22:55:40 +0200 Subject: [PATCH 222/224] Refactor or conditions in DoNotUseInsecureDtdProcessingAnalyzer --- .../DoNotUseInsecureDtdProcessing.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetFramework.Analyzers/DoNotUseInsecureDtdProcessing.cs b/src/NetAnalyzers/Core/Microsoft.NetFramework.Analyzers/DoNotUseInsecureDtdProcessing.cs index 7c50f9b297..e792f8e493 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetFramework.Analyzers/DoNotUseInsecureDtdProcessing.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetFramework.Analyzers/DoNotUseInsecureDtdProcessing.cs @@ -200,7 +200,7 @@ public void AnalyzeOperationBlock(OperationBlockAnalysisContext context) { XmlDocumentEnvironment env = p.Value; - if (!(env.IsXmlResolverSet | env.IsSecureResolver)) + if (!env.IsXmlResolverSet && !env.IsSecureResolver) { context.ReportDiagnostic(env.XmlDocumentDefinition.CreateDiagnostic(RuleXmlDocumentWithNoSecureResolver)); } @@ -209,8 +209,8 @@ public void AnalyzeOperationBlock(OperationBlockAnalysisContext context) foreach (KeyValuePair p in _xmlTextReaderEnvironments) { XmlTextReaderEnvironment env = p.Value; - if (!(env.IsXmlResolverSet | env.IsSecureResolver) || - !(env.IsDtdProcessingSet | env.IsDtdProcessingDisabled)) + if ((!env.IsXmlResolverSet && !env.IsSecureResolver) || + (!env.IsDtdProcessingSet && !env.IsDtdProcessingDisabled)) { context.ReportDiagnostic(env.XmlTextReaderDefinition.CreateDiagnostic(RuleXmlTextReaderConstructedWithNoSecureResolution)); } From 82a9cf1787209462a160c093859876fe232449e7 Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Mon, 16 Aug 2021 23:40:06 +0000 Subject: [PATCH 223/224] Cleaned code for more performance --- .../Runtime/AvoidConstArrays.Fixer.cs | 9 ++++----- .../Runtime/AvoidConstArrays.cs | 10 ++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index fdf15cb050..e41ef63004 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -52,12 +52,11 @@ private static async Task ExtractConstArrayAsync(SyntaxNode node, Sema SyntaxGenerator generator, ImmutableDictionary properties, CancellationToken cancellationToken) { IArrayCreationOperation arrayArgument = GetArrayCreationOperation(node, model, cancellationToken, out bool isInvoked); - INamedTypeSymbol containingType = model.GetEnclosingSymbol(node.SpanStart, cancellationToken).ContainingType; - IEnumerable typeFields = containingType.GetMembers().Where(x => x is IFieldSymbol); + IEnumerable typeFields = model.GetEnclosingSymbol(node.SpanStart, cancellationToken).ContainingType + .GetMembers().Where(x => x is IFieldSymbol); // Get a valid member name for the extracted constant - IEnumerable memberNames = typeFields.Select(x => x.Name); - string newMemberName = GetExtractedMemberName(memberNames, properties["paramName"]); + string newMemberName = GetExtractedMemberName(typeFields.Select(x => x.Name), properties["paramName"]); // Get method containing the symbol that is being diagnosed. Should always be in a method IOperation? containingMethodBody = arrayArgument.GetAncestor(OperationKind.MethodBody); @@ -121,7 +120,7 @@ private static IArrayCreationOperation GetArrayCreationOperation(SyntaxNode node private static string GetExtractedMemberName(IEnumerable memberNames, string parameterName) { string nameOption = parameterName; - bool hasCollectionEnding = collectionMemberEndings.Any(x => nameOption.EndsWith(x, true, System.Globalization.CultureInfo.InvariantCulture)); + bool hasCollectionEnding = collectionMemberEndings.Any(x => nameOption.EndsWith(x, true, null)); if (parameterName == "source" // for LINQ, "sourceArray" is clearer than "source" || (memberNames.Contains(nameOption) && !hasCollectionEnding)) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs index f741f621ea..92a1bdae70 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -43,7 +43,6 @@ public override void Initialize(AnalysisContext context) context.RegisterOperationAction(operationContext => { IArgumentOperation? argumentOperation; - Dictionary properties = new(); if (operationContext.Operation is IArrayCreationOperation arrayCreationOperation) // For arrays passed as arguments { @@ -64,11 +63,11 @@ public override void Initialize(AnalysisContext context) argumentOperation = invocationOperation.Arguments.FirstOrDefault(); if (argumentOperation is null || argumentOperation.Children.First() is not IConversionOperation conversionOperation - || conversionOperation.Operand is not IArrayCreationOperation) + || conversionOperation.Operand is not IArrayCreationOperation arrayCreation) { return; } - arrayCreationOperation = (IArrayCreationOperation)conversionOperation.Operand; + arrayCreationOperation = arrayCreation; } else { @@ -81,7 +80,10 @@ public override void Initialize(AnalysisContext context) return; } - properties["paramName"] = argumentOperation.Parameter.Name; + Dictionary properties = new() + { + { "paramName", argumentOperation.Parameter.Name } + }; operationContext.ReportDiagnostic(arrayCreationOperation.CreateDiagnostic(Rule, properties.ToImmutableDictionary())); }, From 6631760caf4d316c7086dca1792fa56c1c907c35 Mon Sep 17 00:00:00 2001 From: Steve Berdy Date: Tue, 17 Aug 2021 03:43:18 +0000 Subject: [PATCH 224/224] More code cleanup --- .../Runtime/AvoidConstArrays.Fixer.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs index e41ef63004..1d9cf56598 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -25,7 +25,7 @@ public sealed class AvoidConstArraysFixer : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(AvoidConstArraysAnalyzer.RuleId); - private static readonly string[] collectionMemberEndings = new[] { "array", "collection", "enumerable", "list" }; + private static readonly ImmutableArray s_collectionMemberEndings = ImmutableArray.Create("array", "collection", "enumerable", "list"); // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -119,23 +119,25 @@ private static IArrayCreationOperation GetArrayCreationOperation(SyntaxNode node private static string GetExtractedMemberName(IEnumerable memberNames, string parameterName) { - string nameOption = parameterName; - bool hasCollectionEnding = collectionMemberEndings.Any(x => nameOption.EndsWith(x, true, null)); + bool hasCollectionEnding = s_collectionMemberEndings.Any(x => parameterName.EndsWith(x, true, null)); if (parameterName == "source" // for LINQ, "sourceArray" is clearer than "source" - || (memberNames.Contains(nameOption) && !hasCollectionEnding)) + || (memberNames.Contains(parameterName) && !hasCollectionEnding)) { - nameOption += "Array"; + parameterName += "Array"; } - int suffix = 1; - while (memberNames.Contains(nameOption)) + if (memberNames.Contains(parameterName)) { - nameOption += suffix; - suffix++; + int suffix = 0; + while (memberNames.Contains(parameterName + suffix)) + { + suffix++; + } + return parameterName + suffix; } - return nameOption; + return parameterName; } private static Accessibility GetAccessibility(ISymbol originMethodSymbol)