提交 21139223 编写于 作者: C Cyrus Najmabadi

Add features to offer using index and range operators.

上级 5a8dabf9
// 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.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOperator
{
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
internal class CSharpUseIndexOperatorCodeFixProvider : SyntaxEditorBasedCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(IDEDiagnosticIds.UseIndexOperatorDiagnosticId);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
context.RegisterCodeFix(new MyCodeAction(
c => FixAsync(context.Document, context.Diagnostics[0], c)),
context.Diagnostics);
return Task.CompletedTask;
}
protected override Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
SyntaxEditor editor, CancellationToken cancellationToken)
{
foreach (var diagnostic in diagnostics)
{
FixOne(diagnostic, editor, cancellationToken);
}
return Task.CompletedTask;
}
private void FixOne(
Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken)
{
var elementAccess = (ElementAccessExpressionSyntax)diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken);
var value = (ExpressionSyntax)diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken);
editor.ReplaceNode(
elementAccess.ArgumentList.Arguments[0].Expression,
SyntaxFactory.PrefixUnaryExpression(
SyntaxKind.IndexExpression,
value.Parenthesize()));
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Use_index_operator, createChangedDocument, FeaturesResources.Use_index_operator)
{
}
}
}
}
// 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 Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOperator
{
[DiagnosticAnalyzer(LanguageNames.CSharp), Shared]
internal class CSharpUseIndexOperatorDiagnosticAnalyzer : AbstractCodeStyleDiagnosticAnalyzer
{
public CSharpUseIndexOperatorDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseIndexOperatorDiagnosticId,
new LocalizableResourceString(nameof(FeaturesResources.Use_index_operator), FeaturesResources.ResourceManager, typeof(FeaturesResources)),
new LocalizableResourceString(nameof(FeaturesResources.Indexing_can_be_simplified), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
{
}
protected override void InitializeWorker(AnalysisContext context)
{
context.RegisterCompilationStartAction(compilationContext =>
{
var compilation = compilationContext.Compilation;
var stringType = compilation.GetSpecialType(SpecialType.System_String);
var stringIndexer =
stringType.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => IsStringIndexer(p))
.FirstOrDefault();
var stringLength =
stringType.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.Name == nameof(string.Length))
.FirstOrDefault();
if (stringIndexer != null && stringLength != null)
{
compilationContext.RegisterOperationAction(
c => AnalyzePropertyReference(c, stringIndexer, stringLength),
OperationKind.PropertyReference);
}
});
}
private void AnalyzePropertyReference(
OperationAnalysisContext context,
IPropertySymbol stringIndexer, IPropertySymbol stringLength)
{
var cancellationToken = context.CancellationToken;
var propertyReference = (IPropertyReferenceOperation)context.Operation;
if (!stringIndexer.Equals(propertyReference.Property))
{
return;
}
var syntax = propertyReference.Syntax;
if (syntax == null)
{
return;
}
var syntaxTree = syntax.SyntaxTree;
var parseOptions = (CSharpParseOptions)syntaxTree.Options;
//if (parseOptions.LanguageVersion < LanguageVersion.CSharp8)
//{
// return;
//}
var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();
if (optionSet == null)
{
return;
}
var option = optionSet.GetOption(CSharpCodeStyleOptions.PreferIndexOperator);
if (!option.Value)
{
return;
}
// look for `s[s.Length - index.Value]` and convert to `s[^index]`
// Needs to have the one arg for `[s.Length - index.Value]`
if (propertyReference.Instance is null ||
propertyReference.Arguments.Length != 1)
{
return;
}
// Arg needs to be a subtraction for: `s.Length - index.Value`
var arg = propertyReference.Arguments[0];
if (!(arg.Value is IBinaryOperation binaryOperation) ||
binaryOperation.OperatorKind != BinaryOperatorKind.Subtract)
{
return;
}
// Left side of the subtraction needs to be `s.Length`. First make
// sure we're referencing String.Length.
if (!(binaryOperation.LeftOperand is IPropertyReferenceOperation leftPropertyRef) ||
leftPropertyRef.Instance is null ||
!stringLength.Equals(leftPropertyRef.Property))
{
return;
}
// make sure that we're indexing and getting the length off hte same value:
// `s[s.Length`
var indexInstanceSyntax = propertyReference.Instance.Syntax;
var lengthInstanceSyntax = leftPropertyRef.Instance.Syntax;
var syntaxFacts = CSharpSyntaxFactsService.Instance;
if (!syntaxFacts.AreEquivalent(indexInstanceSyntax, lengthInstanceSyntax))
{
return;
}
if (!(propertyReference.Syntax is ElementAccessExpressionSyntax elementAccess))
{
return;
}
var additionalLocations = ImmutableArray.Create(
binaryOperation.RightOperand.Syntax.GetLocation());
context.ReportDiagnostic(
DiagnosticHelper.Create(
Descriptor,
elementAccess.GetLocation(),
option.Notification.Severity,
additionalLocations,
ImmutableDictionary<string, string>.Empty));
}
private static bool IsStringIndexer(IPropertySymbol property)
=> property.IsIndexer && property.Parameters.Length == 1 && property.Parameters[0].Type.SpecialType == SpecialType.System_Int32;
}
}
......@@ -88,6 +88,8 @@ internal static class IDEDiagnosticIds
public const string FormattingDiagnosticId = "IDE0055";
public const string UseIndexOperatorDiagnosticId = "IDE0055";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
public const string AnalyzerDependencyConflictId = "IDE1002";
......
......@@ -1855,6 +1855,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Indexing can be simplified.
/// </summary>
internal static string Indexing_can_be_simplified {
get {
return ResourceManager.GetString("Indexing_can_be_simplified", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Initialize field &apos;{0}&apos;.
/// </summary>
......@@ -3933,6 +3942,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Use index operator.
/// </summary>
internal static string Use_index_operator {
get {
return ResourceManager.GetString("Use_index_operator", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use inferred member name.
/// </summary>
......
......@@ -1469,4 +1469,10 @@ This version used in: {2}</value>
<data name="Fix_formatting" xml:space="preserve">
<value>Fix formatting</value>
</data>
<data name="Indexing_can_be_simplified" xml:space="preserve">
<value>Indexing can be simplified</value>
</data>
<data name="Use_index_operator" xml:space="preserve">
<value>Use index operator</value>
</data>
</root>
\ No newline at end of file
......@@ -61,6 +61,13 @@ private static Option<T> CreateOption<T>(OptionGroup group, string name, T defau
EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_pattern_matching_over_is_with_cast_check"),
new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferPatternMatchingOverIsWithCastCheck)}")});
public static readonly Option<CodeStyleOption<bool>> PreferIndexOperator = CreateOption(
CSharpCodeStyleOptionGroups.ExpressionLevelPreferences, nameof(PreferIndexOperator),
defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement,
storageLocations: new OptionStorageLocation[] {
EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_prefer_index_operator"),
new RoamingProfileStorageLocation("TextEditor.CSharp.Specific.PreferIndexOperator")});
public static readonly CodeStyleOption<ExpressionBodyPreference> NeverWithSilentEnforcement =
new CodeStyleOption<ExpressionBodyPreference>(ExpressionBodyPreference.Never, NotificationOption.Silent);
......@@ -205,6 +212,7 @@ public static IEnumerable<Option<CodeStyleOption<bool>>> GetCodeStyleOptions()
yield return PreferBraces;
yield return PreferSimpleDefaultExpression;
yield return PreferLocalOverAnonymousFunction;
yield return PreferIndexOperator;
}
public static IEnumerable<Option<CodeStyleOption<ExpressionBodyPreference>>> GetExpressionBodyOptions()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册