提交 32081b89 编写于 作者: C Cyrus Najmabadi

UI options. Also support arrays.

上级 ef1482ae
......@@ -306,5 +306,27 @@ void Goo(S s)
}
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestArray()
{
await TestAsync(
@"
class C
{
void Goo(string[] s)
{
var v = s[[||]s.Length - 1];
}
}",
@"
class C
{
void Goo(string[] s)
{
var v = s[^1];
}
}", parseOptions: s_parseOptions);
}
}
}
......@@ -5,6 +5,7 @@
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
using System;
using static Helpers;
internal partial class CSharpUseIndexOperatorDiagnosticAnalyzer
......@@ -19,13 +20,24 @@ private class InfoCache
/// we're using has an indexer that takes an Index.
/// </summary>
private readonly INamedTypeSymbol _indexType;
/// <summary>
/// Mapping from a method like 'MyType.Get(int)' to the Length/Count property for
/// 'MyType' as well as the optional 'MyType.Get(System.Index)' member if it exists.
/// </summary>
private readonly ConcurrentDictionary<IMethodSymbol, MemberInfo> _methodToMemberInfo;
/// <summary>
/// Mapping from an array symbol (like 'int[]') to the Length property for it.
/// </summary>
private readonly ConcurrentDictionary<ITypeSymbol, MemberInfo> _arrayTypeToMemberInfo;
public InfoCache(Compilation compilation)
{
_indexType = compilation.GetTypeByMetadataName("System.Index");
_methodToMemberInfo = new ConcurrentDictionary<IMethodSymbol, MemberInfo>();
_arrayTypeToMemberInfo = new ConcurrentDictionary<ITypeSymbol, MemberInfo>();
// Always allow using System.Index indexers with System.String. The compiler has
// hard-coded knowledge on how to use this type, even if there is no this[Index]
......@@ -38,18 +50,37 @@ public InfoCache(Compilation compilation)
_methodToMemberInfo[indexer.GetMethod] = ComputeMemberInfo(indexer.GetMethod, requireIndexMember: false);
}
public bool TryGetMemberInfo(IMethodSymbol methodSymbol, out MemberInfo memberInfo)
public bool TryGetMemberInfo(
IMethodSymbol methodSymbolOpt, ITypeSymbol typeSymbol, out MemberInfo memberInfo)
{
if (!IsIntIndexingMethod(methodSymbol))
memberInfo = default;
if (methodSymbolOpt != null)
{
if (IsIntIndexingMethod(methodSymbolOpt))
{
memberInfo = _methodToMemberInfo.GetOrAdd(methodSymbolOpt, m => ComputeMemberInfo(m, requireIndexMember: true));
}
}
else if (typeSymbol != null)
{
memberInfo = default;
return false;
memberInfo = _arrayTypeToMemberInfo.GetOrAdd(typeSymbol, t => ComputeMemberInfo(t));
}
memberInfo = _methodToMemberInfo.GetOrAdd(methodSymbol, m => ComputeMemberInfo(m, requireIndexMember: true));
return memberInfo.LengthLikeProperty != null;
}
private MemberInfo ComputeMemberInfo(ITypeSymbol type)
{
if (type is IArrayTypeSymbol)
{
var lengthProperty = GetNoArgInt32Property(type.BaseType, nameof(Array.Length));
return new MemberInfo(lengthProperty, overloadedMethodOpt: null);
}
return default;
}
private MemberInfo ComputeMemberInfo(IMethodSymbol method, bool requireIndexMember)
{
Debug.Assert(IsIntIndexingMethod(method));
......
......@@ -51,10 +51,20 @@ protected override void InitializeWorker(AnalysisContext context)
// compilation. Cache information we compute in this object so we don't have to
// continually recompute it.
var infoCache = new InfoCache(startContext.Compilation);
// Register to hear property references, so we can hear about calls to indexers
// like: s[s.Length - n]
context.RegisterOperationAction(
c => AnalyzePropertyReference(c, infoCache),
OperationKind.PropertyReference);
// Array indexing is represented with a different operation kind. Register
// specifically for that.
context.RegisterOperationAction(
c => AnalyzeArrayElementReference(c, infoCache),
OperationKind.ArrayElementReference);
// Register to hear about methods for: s.Get(s.Length - n)
context.RegisterOperationAction(
c => AnalyzeInvocation(c, infoCache),
OperationKind.Invocation);
......@@ -66,6 +76,11 @@ private void AnalyzeInvocation(OperationAnalysisContext context, InfoCache infoC
var cancellationToken = context.CancellationToken;
var invocationOperation = (IInvocationOperation)context.Operation;
if (invocationOperation.Arguments.Length != 1)
{
return;
}
// Make sure we're actually on an invocation something like `s.Get(...)`.
var invocationSyntax = invocationOperation.Syntax as InvocationExpressionSyntax;
if (invocationSyntax is null)
......@@ -73,13 +88,13 @@ private void AnalyzeInvocation(OperationAnalysisContext context, InfoCache infoC
return;
}
var instance = invocationOperation.Instance;
var targetMethod = invocationOperation.TargetMethod;
var arguments = invocationOperation.Arguments;
AnalyzeInvokedMember(
context, infoCache, invocationOperation,
instance, targetMethod, arguments, cancellationToken);
context, infoCache,
invocationSyntax,
invocationOperation.Instance,
invocationOperation.TargetMethod,
invocationOperation.Arguments[0].Value,
cancellationToken);
}
private void AnalyzePropertyReference(
......@@ -87,10 +102,14 @@ private void AnalyzeInvocation(OperationAnalysisContext context, InfoCache infoC
{
var cancellationToken = context.CancellationToken;
var propertyReference = (IPropertyReferenceOperation)context.Operation;
var property = propertyReference.Property;
// Only analyze indexer calls.
if (!property.IsIndexer)
if (!propertyReference.Property.IsIndexer)
{
return;
}
if (propertyReference.Arguments.Length != 1)
{
return;
}
......@@ -102,22 +121,50 @@ private void AnalyzeInvocation(OperationAnalysisContext context, InfoCache infoC
return;
}
var instance = propertyReference.Instance;
var targetMethod = property.GetMethod;
var arguments = propertyReference.Arguments;
AnalyzeInvokedMember(
context, infoCache,
elementAccess,
propertyReference.Instance,
propertyReference.Property.GetMethod,
propertyReference.Arguments[0].Value,
cancellationToken);
}
private void AnalyzeArrayElementReference(
OperationAnalysisContext context, InfoCache infoCache)
{
var cancellationToken = context.CancellationToken;
var arrayElementReference = (IArrayElementReferenceOperation)context.Operation;
// Has to be a single-dimensional element access.
if (arrayElementReference.Indices.Length != 1)
{
return;
}
// Make sure we're actually on something like `s[...]`.
var elementAccess = arrayElementReference.Syntax as ElementAccessExpressionSyntax;
if (elementAccess is null)
{
return;
}
AnalyzeInvokedMember(
context, infoCache, propertyReference,
instance, targetMethod, arguments, cancellationToken);
context, infoCache,
elementAccess,
arrayElementReference.ArrayReference,
targetMethodOpt: null,
arrayElementReference.Indices[0],
cancellationToken);
}
private void AnalyzeInvokedMember(
OperationAnalysisContext context, InfoCache infoCache, IOperation invocation,
IOperation instance, IMethodSymbol targetMethod, ImmutableArray<IArgumentOperation> arguments,
OperationAnalysisContext context, InfoCache infoCache, SyntaxNode syntax,
IOperation instance, IMethodSymbol targetMethodOpt, IOperation argumentValue,
CancellationToken cancellationToken)
{
// Only supported on C# 8 and above.
var syntaxTree = invocation.Syntax.SyntaxTree;
var syntaxTree = syntax.SyntaxTree;
var parseOptions = (CSharpParseOptions)syntaxTree.Options;
if (parseOptions.LanguageVersion < LanguageVersion.CSharp8)
{
......@@ -142,8 +189,7 @@ private void AnalyzeInvocation(OperationAnalysisContext context, InfoCache infoC
// Needs to have the one arg for `s.Length - value`, and that arg needs to be
// a subtraction.
if (instance is null ||
arguments.Length != 1 ||
!IsSubtraction(arguments[0].Value, out var subtraction))
!IsSubtraction(argumentValue, out var subtraction))
{
return;
}
......@@ -154,7 +200,8 @@ private void AnalyzeInvocation(OperationAnalysisContext context, InfoCache infoC
//
// Also ensure that the left side of the subtraction : `s.Length - value` is actually
// getting the length off the same instance we're indexing into.
if (!infoCache.TryGetMemberInfo(targetMethod, out var memberInfo) ||
if (!infoCache.TryGetMemberInfo(targetMethodOpt, instance.Type, out var memberInfo) ||
!IsInstanceLengthCheck(memberInfo.LengthLikeProperty, instance, subtraction.LeftOperand))
{
return;
......
......@@ -14,7 +14,7 @@ public static IPropertySymbol GetLengthOrCountProperty(INamedTypeSymbol namedTyp
=> GetNoArgInt32Property(namedType, nameof(string.Length)) ??
GetNoArgInt32Property(namedType, nameof(ICollection.Count));
private static IPropertySymbol GetNoArgInt32Property(INamedTypeSymbol type, string name)
public static IPropertySymbol GetNoArgInt32Property(ITypeSymbol type, string name)
=> type.GetMembers(name)
.OfType<IPropertySymbol>()
.Where(p => IsPublicInstance(p) &&
......
......@@ -716,7 +716,6 @@ void M2(string value)
private static readonly string s_preferCompoundAssignments = $@"
using System;
class Customer
{{
void M1(int value)
......@@ -734,6 +733,51 @@ void M2(int value)
//]
}}
}}
";
private static readonly string s_preferIndexOperator = $@"
using System;
class Customer
{{
void M1(string value)
{{
//[
// {ServicesVSResources.Prefer_colon}
var ch = value[^1];
//]
}}
void M2(string value)
{{
//[
// {ServicesVSResources.Over_colon}
var ch = value[value.Length - 1];
//]
}}
}}
";
private static readonly string s_preferRangeOperator = $@"
using System;
class Customer
{{
void M1(string value)
{{
//[
// {ServicesVSResources.Prefer_colon}
var sub = value[1..^1];
//]
}}
void M2(string value)
{{
//[
// {ServicesVSResources.Over_colon}
var sub = value.Substring(1, value.Length - 2);
>>>>>>> UI options. Also support arrays.
//]
}}
}}
";
private static readonly string s_preferIsNullOverReferenceEquals = $@"
......@@ -1193,6 +1237,9 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) :
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferLocalOverAnonymousFunction, ServicesVSResources.Prefer_local_function_over_anonymous_function, s_preferLocalFunctionOverAnonymousFunction, s_preferLocalFunctionOverAnonymousFunction, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferCompoundAssignment, ServicesVSResources.Prefer_compound_assignments, s_preferCompoundAssignments, s_preferCompoundAssignments, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferIndexOperator, ServicesVSResources.Prefer_index_operator, s_preferIndexOperator, s_preferIndexOperator, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferRangeOperator, ServicesVSResources.Prefer_range_operator, s_preferRangeOperator, s_preferRangeOperator, this, optionSet, expressionPreferencesGroupTitle));
AddExpressionBodyOptions(optionSet, expressionPreferencesGroupTitle);
// Variable preferences
......
......@@ -1857,6 +1857,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Prefer index operator.
/// </summary>
internal static string Prefer_index_operator {
get {
return ResourceManager.GetString("Prefer_index_operator", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prefer inferred anonymous type member names.
/// </summary>
......@@ -1920,6 +1929,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Prefer range operator.
/// </summary>
internal static string Prefer_range_operator {
get {
return ResourceManager.GetString("Prefer_range_operator", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prefer readonly.
/// </summary>
......
......@@ -1078,4 +1078,10 @@ I agree to all of the foregoing:</value>
<data name="Kind" xml:space="preserve">
<value>Kind</value>
</data>
<data name="Prefer_index_operator" xml:space="preserve">
<value>Prefer index operator</value>
</data>
<data name="Prefer_range_operator" xml:space="preserve">
<value>Prefer range operator</value>
</data>
</root>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册