提交 c45c71a6 编写于 作者: S Srivatsn Narayanan

Port CA1309 (Use Ordinal StringComparison) to System.Runtime.Analyzers.

The fixer for this rule delves into syntax significantly and so cannot be written with just syntaxgenerator. I've refactored the fixer implementation to share some code between the C#\VB fixer.
上级 126ceaed
......@@ -80,9 +80,7 @@
<Compile Include="Design\CSharpCA1003DiagnosticAnalyzer.cs" />
<Compile Include="Design\CSharpCA1024DiagnosticAnalyzer.cs" />
<Compile Include="Design\CSharpEnumWithFlagsDiagnosticAnalyzer.cs" />
<Compile Include="Globalization\CodeFixes\CA1309CSharpCodeFixProvider.cs" />
<Compile Include="Globalization\CodeFixes\CA2101CSharpCodeFixProvider.cs" />
<Compile Include="Globalization\CSharpCA1309DiagnosticAnalyzer.cs" />
<Compile Include="Performance\CSharpCA1821DiagnosticAnalyzer.cs" />
<Compile Include="Reliability\CSharpCA2002DiagnosticAnalyzer.cs" />
<Compile Include="Usage\CodeFixes\CA2213CSharpCodeFixProvider.cs" />
......
// 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.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.FxCopAnalyzers.Globalization;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.FxCopAnalyzers.Globalization
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = CA1309DiagnosticAnalyzer.RuleId), Shared]
public class CA1309CSharpCodeFixProvider : CA1309CodeFixProviderBase
{
internal override Task<Document> GetUpdatedDocumentAsync(Document document, SemanticModel model, SyntaxNode root, SyntaxNode nodeToFix, Diagnostic diagnostic, CancellationToken cancellationToken)
{
// if nothing can be fixed, return the unchanged node
var newRoot = root;
var kind = nodeToFix.Kind();
var syntaxFactoryService = document.Project.LanguageServices.GetService<SyntaxGenerator>();
switch (kind)
{
case SyntaxKind.Argument:
// StringComparison.CurrentCulture => StringComparison.Ordinal
// StringComparison.CurrentCultureIgnoreCase => StringComparison.OrdinalIgnoreCase
var argument = (ArgumentSyntax)nodeToFix;
var memberAccess = argument.Expression as MemberAccessExpressionSyntax;
if (memberAccess != null)
{
// preserve the "IgnoreCase" suffix if present
bool isIgnoreCase = memberAccess.Name.GetText().ToString().EndsWith(CA1309DiagnosticAnalyzer.IgnoreCaseText, StringComparison.Ordinal);
var newOrdinalText = isIgnoreCase ? CA1309DiagnosticAnalyzer.OrdinalIgnoreCaseText : CA1309DiagnosticAnalyzer.OrdinalText;
var newIdentifier = syntaxFactoryService.IdentifierName(newOrdinalText);
var newMemberAccess = memberAccess.WithName((SimpleNameSyntax)newIdentifier).WithAdditionalAnnotations(Formatter.Annotation);
newRoot = root.ReplaceNode(memberAccess, newMemberAccess);
}
break;
case SyntaxKind.IdentifierName:
// string.Equals(a, b) => string.Equals(a, b, StringComparison.Ordinal)
// string.Compare(a, b) => string.Compare(a, b, StringComparison.Ordinal)
var identifier = (IdentifierNameSyntax)nodeToFix;
var invokeParent = identifier.Parent?.FirstAncestorOrSelf<InvocationExpressionSyntax>();
if (invokeParent != null)
{
var methodSymbol = model.GetSymbolInfo(identifier, cancellationToken).Symbol as IMethodSymbol;
if (methodSymbol != null && CanAddStringComparison(methodSymbol))
{
// append a new StringComparison.Ordinal argument
var newArg = syntaxFactoryService.Argument(CreateOrdinalMemberAccess(syntaxFactoryService, model))
.WithAdditionalAnnotations(Formatter.Annotation);
var newInvoke = invokeParent.AddArgumentListArguments((ArgumentSyntax)newArg).WithAdditionalAnnotations(Formatter.Annotation);
newRoot = root.ReplaceNode(invokeParent, newInvoke);
}
}
break;
case SyntaxKind.EqualsExpression:
case SyntaxKind.NotEqualsExpression:
// "a == b" => "string.Equals(a, b, StringComparison.Ordinal)"
// "a != b" => "!string.Equals(a, b, StringComparison.Ordinal)"
var binaryExpression = (BinaryExpressionSyntax)nodeToFix;
var invocation = CreateEqualsExpression(syntaxFactoryService, model, binaryExpression.Left, binaryExpression.Right, kind == SyntaxKind.EqualsExpression).WithAdditionalAnnotations(Formatter.Annotation);
newRoot = root.ReplaceNode(nodeToFix, invocation);
break;
}
if (newRoot == root)
{
return Task.FromResult(document);
}
return Task.FromResult(document.WithSyntaxRoot(newRoot));
}
}
}
......@@ -11,8 +11,6 @@
-Microsoft.Design#CA1024;
-Microsoft.Design#CA1027;
-Microsoft.Globalization#CA1309;
-Microsoft.Performance#CA1821;
-Microsoft.Reliability#CA2002;
......
......@@ -176,14 +176,5 @@ internal class FxCopFixersResources {
return ResourceManager.GetString("SpecifyMarshalingForPInvokeStringArguments", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to String comparison should use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase.
/// </summary>
internal static string StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase {
get {
return ResourceManager.GetString("StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase", resourceCulture);
}
}
}
}
......@@ -117,9 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase" xml:space="preserve">
<value>String comparison should use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase</value>
</data>
<data name="DisposableFieldsShouldBeDisposed" xml:space="preserve">
<value>Disposable fields should be disposed</value>
</data>
......
......@@ -126,8 +126,6 @@
<DesignTime>True</DesignTime>
<DependentUpon>FxCopRulesResources.resx</DependentUpon>
</Compile>
<Compile Include="Globalization\CA1309DiagnosticAnalyzer.cs" />
<Compile Include="Globalization\CodeFixes\CA1309CodeFixProviderBase.cs" />
<Compile Include="Globalization\CodeFixes\CA2101CodeFixProviderBase.cs" />
<Compile Include="Interoperability\PInvokeDiagnosticAnalyzer.cs" />
<Compile Include="MultipleCodeFixProviderBase.cs" />
......
......@@ -546,24 +546,6 @@ internal class FxCopRulesResources {
}
}
/// <summary>
/// Looks up a localized string similar to For non-linguistic comparisons, StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase should be used instead of the linguistically-sensitive StringComparison.InvariantCulture..
/// </summary>
internal static string StringComparisonShouldBeOrdinalDescription {
get {
return ResourceManager.GetString("StringComparisonShouldBeOrdinalDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to String comparison should use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase.
/// </summary>
internal static string StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase {
get {
return ResourceManager.GetString("StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type &apos;{0}&apos; is abstract but has public constructors.
/// </summary>
......
......@@ -129,9 +129,6 @@
<data name="StaticHolderTypeIsNotStatic" xml:space="preserve">
<value>Type '{0}' is a static holder type but is neither static nor NotInheritable</value>
</data>
<data name="StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase" xml:space="preserve">
<value>String comparison should use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase</value>
</data>
<data name="RethrowException" xml:space="preserve">
<value>Re-throwing caught exception changes stack information.</value>
</data>
......@@ -282,9 +279,6 @@
<data name="SpecifyMarshalingForPInvokeStringArgumentsDescription" xml:space="preserve">
<value>When marshaling strings as ANSI (or as Auto on Win9x), some characters may be changed. If best-fit mapping is on, strings that appear different in Unicode will be marshaled to identical ANSI strings, which may lead to incorrect security decisions. Turning best-fit mapping off reduces this risk, as all characters without equivalents are mapped to '?'. Also, note that CharSet.Ansi is the default setting for all string marshaling; Unicode marshaling must be specified explicitly, either as a CharSet setting of DllImport or StructLayout, or as a MarshalAs attribute with a Unicode (or system-dependent) UnmanagedType.</value>
</data>
<data name="StringComparisonShouldBeOrdinalDescription" xml:space="preserve">
<value>For non-linguistic comparisons, StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase should be used instead of the linguistically-sensitive StringComparison.InvariantCulture.</value>
</data>
<data name="CategoryDesign" xml:space="preserve">
<value>Design</value>
</data>
......
......@@ -21,8 +21,6 @@
-Microsoft.Naming#CA1708;
-Microsoft.Naming#CA1715;
-Microsoft.Performance#CA1813;
-Microsoft.Usage#CA2229;
-Microsoft.Usage#CA2235;
-Microsoft.Usage#CA2237;
......
......@@ -77,6 +77,8 @@
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Compile Include="Globalization\UseOrdinalStringComparison.Fixer.cs" />
<Compile Include="Globalization\UseOrdinalStringComparison.cs" />
<Compile Include="Usage\OverloadOperatorEqualsOnOverridingValueTypeEquals.Fixer.cs" />
<Compile Include="Design\DefineAccessorsForAttributeArguments.cs" />
<Compile Include="Design\OverrideMethodsOnComparableTypes.Fixer.cs" />
......
// 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 System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace System.Runtime.Analyzers
{
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public class CSharpUseOrdinalStringComparisonFixer : UseOrdinalStringComparisonFixerBase
{
protected override bool IsInArgumentContext(SyntaxNode node)
{
return node.IsKind(SyntaxKind.Argument) &&
((ArgumentSyntax)node).Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression);
}
protected override Task<Document> FixArgument(Document document, SyntaxGenerator generator, SyntaxNode root, SyntaxNode argument)
{
var memberAccess = ((ArgumentSyntax)argument)?.Expression as MemberAccessExpressionSyntax;
if (memberAccess != null)
{
// preserve the "IgnoreCase" suffix if present
bool isIgnoreCase = memberAccess.Name.GetText().ToString().EndsWith(UseOrdinalStringComparisonAnalyzer.IgnoreCaseText, StringComparison.Ordinal);
var newOrdinalText = isIgnoreCase ? UseOrdinalStringComparisonAnalyzer.OrdinalIgnoreCaseText : UseOrdinalStringComparisonAnalyzer.OrdinalText;
var newIdentifier = generator.IdentifierName(newOrdinalText);
var newMemberAccess = memberAccess.WithName((SimpleNameSyntax)newIdentifier).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(memberAccess, newMemberAccess);
return Task.FromResult(document.WithSyntaxRoot(newRoot));
}
return Task.FromResult(document);
}
protected override bool IsInIdentifierNameContext(SyntaxNode node)
{
return node.IsKind(SyntaxKind.IdentifierName) &&
node?.Parent?.FirstAncestorOrSelf<InvocationExpressionSyntax>() != null;
}
protected override async Task<Document> FixIdentifierName(Document document, SyntaxGenerator generator, SyntaxNode root, SyntaxNode identifier, CancellationToken cancellationToken)
{
var invokeParent = identifier?.Parent?.FirstAncestorOrSelf<InvocationExpressionSyntax>();
if (invokeParent != null)
{
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var methodSymbol = model.GetSymbolInfo((IdentifierNameSyntax)identifier, cancellationToken).Symbol as IMethodSymbol;
if (methodSymbol != null && CanAddStringComparison(methodSymbol, model))
{
// append a new StringComparison.Ordinal argument
var newArg = generator.Argument(CreateOrdinalMemberAccess(generator, model))
.WithAdditionalAnnotations(Formatter.Annotation);
var newInvoke = invokeParent.AddArgumentListArguments((ArgumentSyntax)newArg).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(invokeParent, newInvoke);
return document.WithSyntaxRoot(newRoot);
}
}
return document;
}
protected override bool IsInEqualsContext(SyntaxNode node)
{
return node.IsKind(SyntaxKind.EqualsExpression) || node.IsKind(SyntaxKind.NotEqualsExpression);
}
protected override async Task<Document> FixEquals(Document document, SyntaxGenerator generator, SyntaxNode root, SyntaxNode node, CancellationToken cancellationToken)
{
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var binaryExpression = (BinaryExpressionSyntax)node;
var invocation = CreateEqualsExpression(generator, model, binaryExpression.Left, binaryExpression.Right, node.Kind() == SyntaxKind.EqualsExpression).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(node, invocation);
return document.WithSyntaxRoot(newRoot);
}
}
}
// 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.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.FxCopAnalyzers.Globalization;
using Microsoft.CodeAnalysis.FxCopAnalyzers.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.FxCopAnalyzers.Globalization
namespace System.Runtime.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CSharpCA1309DiagnosticAnalyzer : CA1309DiagnosticAnalyzer
public class CSharpUseOrdinalStringComparisonAnalyzer : UseOrdinalStringComparisonAnalyzer
{
protected override void GetAnalyzer(CompilationStartAnalysisContext context, INamedTypeSymbol stringComparisonType)
{
......
......@@ -8,6 +8,8 @@
<PropertyGroup>
<CodeAnalysisRuleSetOverrides>$(CodeAnalysisRuleSetOverrides);
-Microsoft.Design#CA1019;
-Microsoft.Globalization#CA1309;
</CodeAnalysisRuleSetOverrides>
</PropertyGroup>
</Project>
\ No newline at end of file
// 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.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.FxCopAnalyzers.Utilities;
namespace Microsoft.CodeAnalysis.FxCopAnalyzers.Globalization
namespace System.Runtime.Analyzers
{
public abstract class CA1309CodeFixProviderBase : CodeFixProviderBase
public abstract class UseOrdinalStringComparisonFixerBase : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(CA1309DiagnosticAnalyzer.RuleId); }
}
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(UseOrdinalStringComparisonAnalyzer.RuleId);
protected sealed override string GetCodeFixDescription(Diagnostic diagnostic)
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
return FxCopFixersResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase;
var syntaxFactoryService = SyntaxGenerator.GetGenerator(context.Document);
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(context.Span);
// We cannot have multiple overlapping diagnostics of this id.
var diagnostic = context.Diagnostics.Single();
if (IsInArgumentContext(node))
{
// StringComparison.CurrentCulture => StringComparison.Ordinal
// StringComparison.CurrentCultureIgnoreCase => StringComparison.OrdinalIgnoreCase
context.RegisterCodeFix(new MyCodeAction(SystemRuntimeAnalyzersResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase,
async ct => await FixArgument(context.Document, syntaxFactoryService, root, node).ConfigureAwait(false)),
diagnostic);
}
else if (IsInIdentifierNameContext(node))
{
// string.Equals(a, b) => string.Equals(a, b, StringComparison.Ordinal)
// string.Compare(a, b) => string.Compare(a, b, StringComparison.Ordinal)
context.RegisterCodeFix(new MyCodeAction(SystemRuntimeAnalyzersResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase,
async ct => await FixIdentifierName(context.Document, syntaxFactoryService, root, node, context.CancellationToken).ConfigureAwait(false)),
diagnostic);
}
else if (IsInEqualsContext(node))
{
// "a == b" => "string.Equals(a, b, StringComparison.Ordinal)"
// "a != b" => "!string.Equals(a, b, StringComparison.Ordinal)"
context.RegisterCodeFix(new MyCodeAction(SystemRuntimeAnalyzersResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase,
async ct => await FixEquals(context.Document, syntaxFactoryService, root, node, context.CancellationToken).ConfigureAwait(false)),
diagnostic);
}
}
protected abstract bool IsInArgumentContext(SyntaxNode node);
protected abstract Task<Document> FixArgument(Document document, SyntaxGenerator syntaxFactoryService, SyntaxNode root, SyntaxNode argument);
protected abstract bool IsInIdentifierNameContext(SyntaxNode node);
protected abstract Task<Document> FixIdentifierName(Document document, SyntaxGenerator syntaxFactoryService, SyntaxNode root, SyntaxNode identifier, CancellationToken cancellationToken);
protected abstract bool IsInEqualsContext(SyntaxNode node);
protected abstract Task<Document> FixEquals(Document document, SyntaxGenerator syntaxFactoryService, SyntaxNode root, SyntaxNode node, CancellationToken cancellationToken);
internal SyntaxNode CreateEqualsExpression(SyntaxGenerator syntaxFactoryService, SemanticModel model, SyntaxNode operand1, SyntaxNode operand2, bool isEquals)
{
var stringType = model.Compilation.GetSpecialType(SpecialType.System_String);
var memberAccess = syntaxFactoryService.MemberAccessExpression(
syntaxFactoryService.TypeExpression(stringType),
syntaxFactoryService.IdentifierName(CA1309DiagnosticAnalyzer.EqualsMethodName));
syntaxFactoryService.IdentifierName(UseOrdinalStringComparisonAnalyzer.EqualsMethodName));
var ordinal = CreateOrdinalMemberAccess(syntaxFactoryService, model);
var invocation = syntaxFactoryService.InvocationExpression(
memberAccess,
......@@ -47,15 +87,20 @@ internal SyntaxNode CreateOrdinalMemberAccess(SyntaxGenerator syntaxFactoryServi
var stringComparisonType = WellKnownTypes.StringComparison(model.Compilation);
return syntaxFactoryService.MemberAccessExpression(
syntaxFactoryService.TypeExpression(stringComparisonType),
syntaxFactoryService.IdentifierName(CA1309DiagnosticAnalyzer.OrdinalText));
syntaxFactoryService.IdentifierName(UseOrdinalStringComparisonAnalyzer.OrdinalText));
}
protected bool CanAddStringComparison(IMethodSymbol methodSymbol)
protected bool CanAddStringComparison(IMethodSymbol methodSymbol, SemanticModel model)
{
if (WellKnownTypes.StringComparison(model.Compilation) == null)
{
return false;
}
var parameters = methodSymbol.Parameters;
switch (methodSymbol.Name)
{
case CA1309DiagnosticAnalyzer.EqualsMethodName:
case UseOrdinalStringComparisonAnalyzer.EqualsMethodName:
// can fix .Equals() with (string), (string, string)
switch (parameters.Length)
{
......@@ -67,7 +112,7 @@ protected bool CanAddStringComparison(IMethodSymbol methodSymbol)
}
break;
case CA1309DiagnosticAnalyzer.CompareMethodName:
case UseOrdinalStringComparisonAnalyzer.CompareMethodName:
// can fix .Compare() with (string, string), (string, int, string, int, int)
switch (parameters.Length)
{
......@@ -87,5 +132,13 @@ protected bool CanAddStringComparison(IMethodSymbol methodSymbol)
return false;
}
private class MyCodeAction : DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
}
// 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 Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.FxCopAnalyzers.Utilities;
using Microsoft.CodeAnalysis;
namespace Microsoft.CodeAnalysis.FxCopAnalyzers.Globalization
namespace System.Runtime.Analyzers
{
public abstract class CA1309DiagnosticAnalyzer : DiagnosticAnalyzer
public abstract class UseOrdinalStringComparisonAnalyzer : DiagnosticAnalyzer
{
internal const string RuleId = "CA1309";
private static LocalizableString s_localizableMessageAndTitle = new LocalizableResourceString(nameof(FxCopRulesResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase), FxCopRulesResources.ResourceManager, typeof(FxCopRulesResources));
private static LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(FxCopRulesResources.StringComparisonShouldBeOrdinalDescription), FxCopRulesResources.ResourceManager, typeof(FxCopRulesResources));
private static LocalizableString s_localizableMessageAndTitle = new LocalizableResourceString(nameof(SystemRuntimeAnalyzersResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase), SystemRuntimeAnalyzersResources.ResourceManager, typeof(SystemRuntimeAnalyzersResources));
private static LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(SystemRuntimeAnalyzersResources.StringComparisonShouldBeOrdinalDescription), SystemRuntimeAnalyzersResources.ResourceManager, typeof(SystemRuntimeAnalyzersResources));
internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId,
s_localizableMessageAndTitle,
s_localizableMessageAndTitle,
FxCopDiagnosticCategory.Globalization,
DiagnosticCategory.Globalization,
DiagnosticSeverity.Warning,
isEnabledByDefault: false,
description: s_localizableDescription,
helpLinkUri: "http://msdn.microsoft.com/library/bb385972.aspx",
customTags: DiagnosticCustomTags.Microsoft);
customTags: WellKnownDiagnosticTags.Telemetry);
internal const string CompareMethodName = "Compare";
internal const string EqualsMethodName = "Equals";
......@@ -32,13 +31,7 @@ public abstract class CA1309DiagnosticAnalyzer : DiagnosticAnalyzer
protected abstract void GetAnalyzer(CompilationStartAnalysisContext context, INamedTypeSymbol stringComparisonType);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
return ImmutableArray.Create(Rule);
}
}
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext analysisContext)
{
......
......@@ -15,6 +15,8 @@
-Microsoft.Design#CA1018;
-Microsoft.Design#CA1036;
-Microsoft.Performance#CA1813;
-Microsoft.Usage#CA2231;
</CodeAnalysisRuleSetOverrides>
</PropertyGroup>
......
......@@ -68,6 +68,8 @@
<Compile Include="Design\TypesThatOwnDisposableFieldsShouldBeDisposable.Fixer.cs" />
<Compile Include="Design\TypesThatOwnDisposableFieldsShouldBeDisposable.cs" />
<Compile Include="DiagnosticCategory.cs" />
<Compile Include="Globalization\UseOrdinalStringComparison.Fixer.cs" />
<Compile Include="Globalization\UseOrdinalStringComparison.cs" />
<Compile Include="Performance\AvoidUnsealedAttributes.cs" />
<Compile Include="Performance\AvoidUnsealedAttributes.Fixer.cs" />
<Compile Include="Shared\DiagnosticExtensions.cs" />
......
......@@ -358,6 +358,24 @@ internal class SystemRuntimeAnalyzersResources {
}
}
/// <summary>
/// Looks up a localized string similar to For non-linguistic comparisons, StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase should be used instead of the linguistically-sensitive StringComparison.InvariantCulture..
/// </summary>
internal static string StringComparisonShouldBeOrdinalDescription {
get {
return ResourceManager.GetString("StringComparisonShouldBeOrdinalDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to String comparison should use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase.
/// </summary>
internal static string StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase {
get {
return ResourceManager.GetString("StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type &apos;{0}&apos; owns disposable fields but is not disposable.
/// </summary>
......
......@@ -222,4 +222,10 @@
<data name="AvoidUnsealedAttributesCodeFix" xml:space="preserve">
<value>Seal attribute type.</value>
</data>
<data name="StringComparisonShouldBeOrdinalDescription" xml:space="preserve">
<value>For non-linguistic comparisons, StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase should be used instead of the linguistically-sensitive StringComparison.InvariantCulture.</value>
</data>
<data name="StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase" xml:space="preserve">
<value>String comparison should use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase</value>
</data>
</root>
\ No newline at end of file
// 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.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.FxCopAnalyzers.Globalization;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.VisualBasic.FxCopAnalyzers.Globalization;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.UnitTests.Globalization
namespace System.Runtime.Analyzers.UnitTests
{
[WorkItem(858659)]
public class CA1309FixerTests : CodeFixTestBase
......@@ -17,22 +16,22 @@ public class CA1309FixerTests : CodeFixTestBase
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
{
return new CSharpCA1309DiagnosticAnalyzer();
return new CSharpUseOrdinalStringComparisonAnalyzer();
}
protected override DiagnosticAnalyzer GetBasicDiagnosticAnalyzer()
{
return new BasicCA1309DiagnosticAnalyzer();
return new BasicUseOrdinalStringComparisonAnalyzer();
}
protected override CodeFixProvider GetCSharpCodeFixProvider()
{
return new CA1309CSharpCodeFixProvider();
return new CSharpUseOrdinalStringComparisonFixer();
}
protected override CodeFixProvider GetBasicCodeFixProvider()
{
return new CA1309BasicCodeFixProvider();
return new BasicUseOrdinalStringComparisonFixer();
}
#endregion
......
// 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 Microsoft.CodeAnalysis.CSharp.FxCopAnalyzers.Globalization;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.FxCopAnalyzers;
using Microsoft.CodeAnalysis.FxCopAnalyzers.Globalization;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.VisualBasic.FxCopAnalyzers.Globalization;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.UnitTests.Globalization
namespace System.Runtime.Analyzers.UnitTests
{
[WorkItem(858659, "DevDiv")]
public class CA1309Tests : DiagnosticAnalyzerTestBase
......@@ -19,22 +15,22 @@ public class CA1309Tests : DiagnosticAnalyzerTestBase
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
{
return new CSharpCA1309DiagnosticAnalyzer();
return new CSharpUseOrdinalStringComparisonAnalyzer();
}
protected override DiagnosticAnalyzer GetBasicDiagnosticAnalyzer()
{
return new BasicCA1309DiagnosticAnalyzer();
return new BasicUseOrdinalStringComparisonAnalyzer();
}
private static DiagnosticResult CSharpResult(int line, int column)
{
return GetCSharpResultAt(line, column, CA1309DiagnosticAnalyzer.RuleId, FxCopRulesResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase);
return GetCSharpResultAt(line, column, UseOrdinalStringComparisonAnalyzer.RuleId, SystemRuntimeAnalyzersResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase);
}
private static DiagnosticResult BasicResult(int line, int column)
{
return GetBasicResultAt(line, column, CA1309DiagnosticAnalyzer.RuleId, FxCopRulesResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase);
return GetBasicResultAt(line, column, UseOrdinalStringComparisonAnalyzer.RuleId, SystemRuntimeAnalyzersResources.StringComparisonShouldBeOrdinalOrOrdinalIgnoreCase);
}
#endregion
......
......@@ -98,6 +98,8 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Globalization\UseOrdinalStringComparisonTests.Fixer.cs" />
<Compile Include="Globalization\UseOrdinalStringComparisonTests.cs" />
<Compile Include="Performance\AvoidUnsealedAttributesTests.cs" />
<Compile Include="Design\MarkAllAssembliesWithComVisibleTests.cs" />
<Compile Include="Design\MarkAssembliesWithCLSCompliantAttributeTests.cs" />
......
......@@ -77,6 +77,8 @@
<ItemGroup>
<Compile Include="Design\DefineAccessorsForAttributeArguments.vb" />
<Compile Include="Design\OverrideMethodsOnComparableTypes.Fixer.vb" />
<Compile Include="Globalization\UseOrdinalStringComparison.Fixer.vb" />
<Compile Include="Globalization\UseOrdinalStringComparison.vb" />
<Compile Include="Usage\OverloadOperatorEqualsOnOverridingValueTypeEquals.Fixer.vb" />
</ItemGroup>
<ImportGroup Label="Targets">
......
' 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 System.Threading
Imports System.Threading.Tasks
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Shared.Extensions
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace System.Runtime.Analyzers
<ExportCodeFixProvider(LanguageNames.VisualBasic), [Shared]>
Public Class BasicUseOrdinalStringComparisonFixer
Inherits UseOrdinalStringComparisonFixerBase
Protected Overrides Function IsInArgumentContext(node As SyntaxNode) As Boolean
Return node.IsKind(SyntaxKind.SimpleArgument) AndAlso
Not DirectCast(node, SimpleArgumentSyntax).IsNamed AndAlso
DirectCast(node, SimpleArgumentSyntax).Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)
End Function
Protected Overrides Function FixArgument(document As Document, generator As SyntaxGenerator, root As SyntaxNode, argument As SyntaxNode) As Task(Of Document)
Dim memberAccess = TryCast(TryCast(argument, SimpleArgumentSyntax)?.Expression, MemberAccessExpressionSyntax)
If memberAccess IsNot Nothing Then
' preserve the "IgnoreCase" suffix if present
Dim isIgnoreCase = memberAccess.Name.GetText().ToString().EndsWith(UseOrdinalStringComparisonAnalyzer.IgnoreCaseText, StringComparison.Ordinal)
Dim newOrdinalText = If(isIgnoreCase, UseOrdinalStringComparisonAnalyzer.OrdinalIgnoreCaseText, UseOrdinalStringComparisonAnalyzer.OrdinalText)
Dim newIdentifier = generator.IdentifierName(newOrdinalText)
Dim newMemberAccess = memberAccess.WithName(CType(newIdentifier, SimpleNameSyntax)).WithAdditionalAnnotations(Formatter.Annotation)
Dim newRoot = root.ReplaceNode(memberAccess, newMemberAccess)
Return Task.FromResult(document.WithSyntaxRoot(newRoot))
End If
Return Task.FromResult(document)
End Function
Protected Overrides Function IsInIdentifierNameContext(node As SyntaxNode) As Boolean
Return node.IsKind(SyntaxKind.IdentifierName) AndAlso
node?.Parent?.FirstAncestorOrSelf(Of InvocationExpressionSyntax)() IsNot Nothing
End Function
Protected Overrides Async Function FixIdentifierName(document As Document, generator As SyntaxGenerator, root As SyntaxNode, identifier As SyntaxNode, cancellationToken As CancellationToken) As Task(Of Document)
Dim invokeParent = identifier.Parent?.FirstAncestorOrSelf(Of InvocationExpressionSyntax)()
If invokeParent IsNot Nothing Then
Dim model = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
Dim methodSymbol = TryCast(model.GetSymbolInfo(identifier).Symbol, IMethodSymbol)
If methodSymbol IsNot Nothing AndAlso CanAddStringComparison(methodSymbol, model) Then
' append a New StringComparison.Ordinal argument
Dim newArg = generator.Argument(CreateOrdinalMemberAccess(generator, model)).
WithAdditionalAnnotations(Formatter.Annotation)
Dim newInvoke = invokeParent.AddArgumentListArguments(CType(newArg, ArgumentSyntax)).WithAdditionalAnnotations(Formatter.Annotation)
Dim newRoot = root.ReplaceNode(invokeParent, newInvoke)
Return document.WithSyntaxRoot(newRoot)
End If
End If
Return document
End Function
Protected Overrides Function IsInEqualsContext(node As SyntaxNode) As Boolean
Return node.IsKind(SyntaxKind.EqualsExpression) OrElse node.IsKind(SyntaxKind.NotEqualsExpression)
End Function
Protected Overrides Async Function FixEquals(document As Document, generator As SyntaxGenerator, root As SyntaxNode, node As SyntaxNode, cancellationToken As CancellationToken) As Task(Of Document)
Dim model = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
Dim binaryExpression = CType(node, BinaryExpressionSyntax)
Dim fixedExpr = CreateEqualsExpression(generator, model, binaryExpression.Left, binaryExpression.Right, binaryExpression.IsKind(SyntaxKind.EqualsExpression)).WithAdditionalAnnotations(Formatter.Annotation)
Dim newRoot = root.ReplaceNode(node, fixedExpr)
Return document.WithSyntaxRoot(newRoot)
End Function
End Class
End Namespace
' 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.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.FxCopAnalyzers.Globalization
Imports Microsoft.CodeAnalysis.FxCopAnalyzers.Utilities
Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.FxCopAnalyzers.Globalization
Namespace System.Runtime.Analyzers
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Public Class BasicCA1309DiagnosticAnalyzer
Inherits CA1309DiagnosticAnalyzer
Public Class BasicUseOrdinalStringComparisonAnalyzer
Inherits UseOrdinalStringComparisonAnalyzer
Protected Overrides Sub GetAnalyzer(context As CompilationStartAnalysisContext, stringComparisonType As INamedTypeSymbol)
context.RegisterSyntaxNodeAction(AddressOf New Analyzer(stringComparisonType).AnalyzeNode, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, SyntaxKind.InvocationExpression)
......
......@@ -8,6 +8,8 @@
<PropertyGroup>
<CodeAnalysisRuleSetOverrides>$(CodeAnalysisRuleSetOverrides);
-Microsoft.Design#CA1019;
-Microsoft.Globalization#CA1309;
</CodeAnalysisRuleSetOverrides>
</PropertyGroup>
</Project>
\ No newline at end of file
......@@ -92,8 +92,6 @@
<Compile Include="Design\CodeFixes\CA1012FixerTests.cs" />
<Compile Include="Design\CodeFixes\EnumWithFlagsAttributesRulesFixerTests.cs" />
<Compile Include="Design\EnumWithFlagsAttributeRulesTests.cs" />
<Compile Include="Globalization\CA1309Tests.cs" />
<Compile Include="Globalization\CodeFixes\CA1309FixerTests.cs" />
<Compile Include="Globalization\CodeFixes\CA2101FixerTests.cs" />
<Compile Include="HardeningAnalyzer\HardeningAnalyzerTests.cs" />
<Compile Include="Interoperability\PInvokeDiagnosticAnalyzerTests.cs" />
......
......@@ -102,8 +102,6 @@
<Compile Include="Design\CodeFixes\CA1008BasicCodeFixProvider.vb" />
<Compile Include="Design\CodeFixes\CA1052BasicCodeFixProvider.vb" />
<Compile Include="Design\CodeFixes\EnumWithFlagsBasicCodeFixProvider.vb" />
<Compile Include="Globalization\BasicCA1309DiagnosticAnalyzer.vb" />
<Compile Include="Globalization\CodeFixes\CA1309BasicCodeFixProvider.vb" />
<Compile Include="Globalization\CodeFixes\CA2101BasicCodeFixProvider.vb" />
<Compile Include="Performance\BasicCA1821DiagnosticAnalyzer.vb" />
<Compile Include="Reliability\BasicCA2002DiagnosticAnalyzer.vb" />
......
' 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 System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.FxCopAnalyzers.Globalization
Imports Microsoft.CodeAnalysis.Shared.Extensions
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.FxCopAnalyzers.Globalization
<ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=CA1309DiagnosticAnalyzer.RuleId), [Shared]>
Public Class CA1309BasicCodeFixProvider
Inherits CA1309CodeFixProviderBase
Friend Overrides Function GetUpdatedDocumentAsync(document As Document, model As SemanticModel, root As SyntaxNode, nodeToFix As SyntaxNode, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document)
' if nothing can be fixed, return the unchanged node
Dim newRoot = root
Dim kind = nodeToFix.Kind()
Dim syntaxFactoryService = document.Project.LanguageServices.GetService(Of SyntaxGenerator)
Select Case kind
Case SyntaxKind.SimpleArgument
If Not CType(nodeToFix, SimpleArgumentSyntax).IsNamed Then
' StringComparison.CurrentCulture => StringComparison.Ordinal
' StringComparison.CurrentCultureIgnoreCase => StringComparison.OrdinalIgnoreCase
Dim argument = CType(nodeToFix, SimpleArgumentSyntax)
Dim memberAccess = TryCast(argument.Expression, MemberAccessExpressionSyntax)
If memberAccess IsNot Nothing Then
' preserve the "IgnoreCase" suffix if present
Dim isIgnoreCase = memberAccess.Name.GetText().ToString().EndsWith(CA1309DiagnosticAnalyzer.IgnoreCaseText, StringComparison.Ordinal)
Dim newOrdinalText = If(isIgnoreCase, CA1309DiagnosticAnalyzer.OrdinalIgnoreCaseText, CA1309DiagnosticAnalyzer.OrdinalText)
Dim newIdentifier = syntaxFactoryService.IdentifierName(newOrdinalText)
Dim newMemberAccess = memberAccess.WithName(CType(newIdentifier, SimpleNameSyntax)).WithAdditionalAnnotations(Formatter.Annotation)
newRoot = root.ReplaceNode(memberAccess, newMemberAccess)
End If
End If
Case SyntaxKind.IdentifierName
' String.Equals(a, b) => String.Equals(a, b, StringComparison.Ordinal)
' String.Compare(a, b) => String.Compare(a, b, StringComparison.Ordinal)
Dim identifier = CType(nodeToFix, IdentifierNameSyntax)
Dim invokeParent = identifier.Parent?.FirstAncestorOrSelf(Of InvocationExpressionSyntax)()
If invokeParent IsNot Nothing Then
Dim methodSymbol = TryCast(model.GetSymbolInfo(identifier).Symbol, IMethodSymbol)
If methodSymbol IsNot Nothing AndAlso CanAddStringComparison(methodSymbol) Then
' append a New StringComparison.Ordinal argument
Dim newArg = syntaxFactoryService.Argument(CreateOrdinalMemberAccess(syntaxFactoryService, model)).
WithAdditionalAnnotations(Formatter.Annotation)
Dim newInvoke = invokeParent.AddArgumentListArguments(CType(newArg, ArgumentSyntax)).WithAdditionalAnnotations(Formatter.Annotation)
newRoot = root.ReplaceNode(invokeParent, newInvoke)
End If
End If
Case SyntaxKind.EqualsExpression
' "a = b" => "String.Equals(a, b, StringComparison.Ordinal)"
Dim fixedExpr = FixBinaryExpression(syntaxFactoryService, model, CType(nodeToFix, BinaryExpressionSyntax), True).WithAdditionalAnnotations(Formatter.Annotation)
newRoot = root.ReplaceNode(nodeToFix, fixedExpr)
Case SyntaxKind.NotEqualsExpression
' "a <> b" => "!String.Equals(a, b, StringComparison.Ordinal)"
Dim fixedExpr = FixBinaryExpression(syntaxFactoryService, model, CType(nodeToFix, BinaryExpressionSyntax), False).WithAdditionalAnnotations(Formatter.Annotation)
newRoot = root.ReplaceNode(nodeToFix, fixedExpr)
End Select
If newRoot.Equals(root) Then
Return Task.FromResult(document)
End If
Return Task.FromResult(document.WithSyntaxRoot(newRoot))
End Function
Private Function FixBinaryExpression(syntaxFactoryService As SyntaxGenerator, model As SemanticModel, node As BinaryExpressionSyntax, isEquals As Boolean) As SyntaxNode
Dim invocation = CreateEqualsExpression(syntaxFactoryService, model, node.Left, node.Right, isEquals)
Return invocation
End Function
End Class
End Namespace
......@@ -11,8 +11,6 @@
-Microsoft.Design#CA1024;
-Microsoft.Design#CA1027;
-Microsoft.Globalization#CA1309;
-Microsoft.Performance#CA1821;
-Microsoft.Reliability#CA2002;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册