未验证 提交 3075aec3 编写于 作者: M Manish Vasani 提交者: GitHub

Merge pull request #30635 from CyrusNajmabadi/useIndexOperator

Add features to offer using index and range operators.
// 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.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseIndexOrRangeOperator
{
public class UseIndexOperatorTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (new CSharpUseIndexOperatorDiagnosticAnalyzer(), new CSharpUseIndexOperatorCodeFixProvider());
private static readonly CSharpParseOptions s_parseOptions =
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8);
private static readonly TestParameters s_testParameters =
new TestParameters(parseOptions: s_parseOptions);
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNotInCSharp7()
{
await TestMissingAsync(
@"
class C
{
void Goo(string s)
{
var v = s[[||]s.Length - 1];
}
}", parameters: new TestParameters(
parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7)));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestSimple()
{
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);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestComplexSubtaction()
{
await TestAsync(
@"
class C
{
void Goo(string s)
{
var v = s[[||]s.Length - (1 + 1)];
}
}",
@"
class C
{
void Goo(string s)
{
var v = s[^(1 + 1)];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestComplexInstance()
{
await TestAsync(
@"
using System.Linq;
class C
{
void Goo(string[] ss)
{
var v = ss.Last()[[||]ss.Last().Length - 3];
}
}",
@"
using System.Linq;
class C
{
void Goo(string[] ss)
{
var v = ss.Last()[^3];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNotWithoutSubtraction1()
{
await TestMissingAsync(
@"
class C
{
void Goo(string s)
{
var v = s[[||]s.Length];
}
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNotWithoutSubtraction2()
{
await TestMissingAsync(
@"
class C
{
void Goo(string s)
{
var v = s[[||]s.Length + 1];
}
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNotWithMultipleArgs()
{
await TestMissingAsync(
@"
class C
{
void Goo(string s)
{
var v = s[[||]s.Length - 1, 2];
}
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithLength()
{
await TestAsync(
@"
namespace System { public struct Index { } }
struct S { public int Length { get; } public int this[int i] { get; } public int this[System.Index i] { get; } }
class C
{
void Goo(S s)
{
var v = s[[||]s.Length - 2];
}
}",
@"
namespace System { public struct Index { } }
struct S { public int Length { get; } public int this[int i] { get; } public int this[System.Index i] { get; } }
class C
{
void Goo(S s)
{
var v = s[^2];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithCount()
{
await TestAsync(
@"
namespace System { public struct Index { } }
struct S { public int Count { get; } public int this[int i] { get; } public int this[System.Index i] { get; } }
class C
{
void Goo(S s)
{
var v = s[[||]s.Count - 2];
}
}",
@"
namespace System { public struct Index { } }
struct S { public int Count { get; } public int this[int i] { get; } public int this[System.Index i] { get; } }
class C
{
void Goo(S s)
{
var v = s[^2];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithNoLengthOrCount()
{
await TestMissingAsync(
@"
namespace System { public struct Index { } }
struct S { public int this[int i] { get; } public int this[System.Index i] { get; } }
class C
{
void Goo(S s)
{
var v = s[[||]s.Count - 2];
}
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithNoInt32Indexer()
{
await TestMissingAsync(
@"
namespace System { public struct Index { } }
struct S { public int Length { get; } public int this[System.Index i] { get; } }
class C
{
void Goo(S s)
{
var v = s[[||]s.Count - 2];
}
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithNoIndexIndexer()
{
await TestMissingAsync(
@"
namespace System { public struct Index { } }
struct S { public int Length { get; } public int this[int i] { get; } }
class C
{
void Goo(S s)
{
var v = s[[||]s.Count - 2];
}
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestMethodToMethod()
{
await TestAsync(
@"
namespace System { class Index { } }
struct S { public int Length { get; } public int Get(int i); public int Get(System.Index i); }
class C
{
void Goo(S s)
{
var v = s.Get([||]s.Length - 1);
}
}",
@"
namespace System { class Index { } }
struct S { public int Length { get; } public int Get(int i); public int Get(System.Index i); }
class C
{
void Goo(S s)
{
var v = s.Get(^1);
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestMethodToMethodMissingIndexIndexer()
{
await TestMissingAsync(
@"
namespace System { class Index { } }
struct S { public int Length { get; } public int Get(int i); }
class C
{
void Goo(S s)
{
var v = s.Get([||]s.Length - 1);
}
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestMethodToMethodWithIntIndexer()
{
await TestMissingAsync(
@"
namespace System { class Index { } }
struct S { public int Length { get; } public int Get(int i); public int this[int i] { get; } }
class C
{
void Goo(S s)
{
var v = s.Get([||]s.Length - 1);
}
}", 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);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestFixAll1()
{
await TestAsync(
@"
class C
{
void Goo(string s)
{
var v1 = s[{|FixAllInDocument:|}s.Length - 1];
var v2 = s[s.Length - 1];
}
}",
@"
class C
{
void Goo(string s)
{
var v1 = s[^1];
var v2 = s[^1];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestFixAll2()
{
await TestAsync(
@"
class C
{
void Goo(string s)
{
var v1 = s[s.Length - 1];
var v2 = s[{|FixAllInDocument:|}s.Length - 1];
}
}",
@"
class C
{
void Goo(string s)
{
var v1 = s[^1];
var v2 = s[^1];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNestedFixAll1()
{
await TestAsync(
@"
class C
{
void Goo(string[] s)
{
var v1 = s[s.Length - 2][s[{|FixAllInDocument:|}s.Length - 2].Length - 1];
}
}",
@"
class C
{
void Goo(string[] s)
{
var v1 = s[^2][^1];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNestedFixAll2()
{
await TestAsync(
@"
class C
{
void Goo(string[] s)
{
var v1 = s[{|FixAllInDocument:|}s.Length - 2][s[s.Length - 2].Length - 1];
}
}",
@"
class C
{
void Goo(string[] s)
{
var v1 = s[^2][^1];
}
}", parseOptions: s_parseOptions);
}
}
}
// 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.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseIndexOrRangeOperator
{
public class UseRangeOperatorTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (new CSharpUseRangeOperatorDiagnosticAnalyzer(), new CSharpUseRangeOperatorCodeFixProvider());
private static readonly CSharpParseOptions s_parseOptions =
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8);
private static readonly TestParameters s_testParameters =
new TestParameters(parseOptions: s_parseOptions);
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestNotInCSharp7()
{
await TestMissingAsync(
@"
class C
{
void Goo(string s)
{
var v = s.Substring([||]1, s.Length - 1);
}
}", parameters: new TestParameters(
parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7)));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestSimple()
{
await TestAsync(
@"
class C
{
void Goo(string s)
{
var v = s.Substring([||]1, s.Length - 1);
}
}",
@"
class C
{
void Goo(string s)
{
var v = s[1..];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestComplexSubstraction()
{
await TestAsync(
@"
class C
{
void Goo(string s, int bar, int baz)
{
var v = s.Substring([||]bar, s.Length - baz - bar);
}
}",
@"
class C
{
void Goo(string s, int bar, int baz)
{
var v = s[bar..^baz];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestConstantSubtraction1()
{
await TestAsync(
@"
class C
{
void Goo(string s)
{
var v = s.Substring([||]1, s.Length - 2);
}
}",
@"
class C
{
void Goo(string s)
{
var v = s[1..^1];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestNonStringType()
{
await TestAsync(
@"
namespace System { public struct Range { } }
struct S { public S Slice(int start, int length); public int Length { get; } public S this[System.Range] { get; } }
class C
{
void Goo(S s)
{
var v = s.Slice([||]1, s.Length - 2);
}
}",
@"
namespace System { public struct Range { } }
struct S { public S Slice(int start, int length); public int Length { get; } public S this[System.Range] { get; } }
class C
{
void Goo(S s)
{
var v = s[1..^1];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestMethodToMethod()
{
await TestAsync(
@"
namespace System { public struct Range { } }
struct S { public int Slice(int start, int length); public int Length { get; } public int Slice(System.Range r); }
class C
{
void Goo(S s)
{
var v = s.Slice([||]1, s.Length - 2);
}
}",
@"
namespace System { public struct Range { } }
struct S { public int Slice(int start, int length); public int Length { get; } public int Slice(System.Range r); }
class C
{
void Goo(S s)
{
var v = s.Slice(1..^1);
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestFixAllInvocationToElementAccess1()
{
// Note: once the IOp tree has support for range operators, this should
// simplify even further.
await TestAsync(
@"
class C
{
void Goo(string s, string t)
{
var v = t.Substring(s.Substring({|FixAllInDocument:|}1, s.Length - 2)[0], t.Length - s.Substring(1, s.Length - 2)[0]);
}
}",
@"
class C
{
void Goo(string s, string t)
{
var v = t.Substring(s[1..^1][0], t.Length - s[1..^1][0]);
}
}", parseOptions: s_parseOptions);
}
}
}
// 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.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
using System.Linq;
using static Helpers;
[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)
{
// Process diagnostics from innermost to outermost in case any are nested.
foreach (var diagnostic in diagnostics.OrderByDescending(d => d.Location.SourceSpan.Start))
{
var node = diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken);
editor.ReplaceNode(
node,
(currentNode, _) => IndexExpression(((BinaryExpressionSyntax)currentNode).Right));
}
return Task.CompletedTask;
}
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.Concurrent;
using System.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
using System;
using static Helpers;
internal partial class CSharpUseIndexOperatorDiagnosticAnalyzer
{
/// <summary>
/// Helper type to cache information about types while analyzing the compilation.
/// </summary>
private class InfoCache
{
/// <summary>
/// The System.Index type. Needed so that we only fixup code if we see the type
/// 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;
public InfoCache(Compilation compilation)
{
_indexType = compilation.GetTypeByMetadataName("System.Index");
_methodToMemberInfo = new ConcurrentDictionary<IMethodSymbol, 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]
// indexer declared on it directly.
var stringType = compilation.GetSpecialType(SpecialType.System_String);
var indexer = GetIndexer(stringType,
compilation.GetSpecialType(SpecialType.System_Int32),
compilation.GetSpecialType(SpecialType.System_Char));
_methodToMemberInfo[indexer.GetMethod] = ComputeMemberInfo(indexer.GetMethod, requireIndexMember: false);
}
public bool TryGetMemberInfo(IMethodSymbol methodSymbol, out MemberInfo memberInfo)
{
memberInfo = default;
if (IsIntIndexingMethod(methodSymbol))
{
memberInfo = _methodToMemberInfo.GetOrAdd(methodSymbol, m => ComputeMemberInfo(m, requireIndexMember: true));
}
return memberInfo.LengthLikeProperty != null;
}
private MemberInfo ComputeMemberInfo(IMethodSymbol method, bool requireIndexMember)
{
Debug.Assert(IsIntIndexingMethod(method));
// Check that the type has an int32 'Length' or 'Count' property. If not, we don't
// consider it something indexable.
var containingType = method.ContainingType;
var lengthLikeProperty = TryGetLengthOrCountProperty(containingType);
if (lengthLikeProperty == null)
{
return default;
}
if (!requireIndexMember)
{
return new MemberInfo(lengthLikeProperty, overloadedMethodOpt: null);
}
if (method.MethodKind == MethodKind.PropertyGet)
{
// this is the getter for an indexer. i.e. the user is calling something
// like s[...]. We need to see if there's an indexer that takes a System.Index
// value.
var indexer = GetIndexer(containingType, _indexType, method.ReturnType);
if (indexer != null)
{
// Type had a matching indexer. We can convert calls to the int-indexer to
// calls to this System.Index-indexer.
return new MemberInfo(lengthLikeProperty, overloadedMethodOpt: null);
}
}
else
{
Debug.Assert(method.MethodKind == MethodKind.Ordinary);
// it's a method like: `SomeType MyType.Get(int index)`. Look
// for an overload like: `SomeType MyType.Get(Range)`
var overloadedIndexMethod = GetOverload(method, _indexType);
if (overloadedIndexMethod != null)
{
return new MemberInfo(lengthLikeProperty, overloadedIndexMethod);
}
}
// A index-like method that we can't convert.
return default;
}
}
}
}
// 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 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.UseIndexOrRangeOperator
{
using static Helpers;
/// <summary>
/// Analyzer that looks for code like:
///
/// 1) `s[s.Length - n]` and offers to change that to `s[^n]`. and.
/// 2) `s.Get(s.Length - n)` and offers to change that to `s.Get(^n)`
///
/// In order to do convert between indexers, the type must look 'indexable'. Meaning, it must
/// have an int-returning property called 'Length' or 'Count', and it must have both an
/// int-indexer, and a System.Index-indexer. In order to convert between methods, the type
/// must have identical overloads except that one takes an int, and the other a System.Index.
///
/// It is assumed that if the type follows this shape that it is well behaved and that this
/// transformation will preserve semantics. If this assumption is not good in practice, we
/// could always limit the feature to only work on a whitelist of known safe types.
///
/// Note that this feature only works if the code literally has `expr1.Length - expr2`. If
/// code has this, and is calling into a method that takes either an int or a System.Index,
/// it feels very safe to assume this is well behaved and switching to `^expr2` is going to
/// preserve semantics.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp), Shared]
internal partial class CSharpUseIndexOperatorDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
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)))
{
}
public override bool OpenFileOnly(Workspace workspace) => false;
public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
protected override void InitializeWorker(AnalysisContext context)
{
context.RegisterCompilationStartAction(startContext =>
{
// We're going to be checking every property-reference and invocation in the
// compilation. Cache information we compute in this object so we don't have to
// continually recompute it.
var compilation = startContext.Compilation;
var infoCache = new InfoCache(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);
// Register to hear about methods for: s.Get(s.Length - n)
context.RegisterOperationAction(
c => AnalyzeInvocation(c, infoCache),
OperationKind.Invocation);
var arrayType = compilation.GetSpecialType(SpecialType.System_Array);
var arrayLengthProperty = TryGetNoArgInt32Property(arrayType, nameof(Array.Length));
if (arrayLengthProperty != null)
{
// Array indexing is represented with a different operation kind. Register
// specifically for that.
context.RegisterOperationAction(
c => AnalyzeArrayElementReference(c, infoCache, arrayLengthProperty),
OperationKind.ArrayElementReference);
}
});
}
private void AnalyzeInvocation(
OperationAnalysisContext context, InfoCache infoCache)
{
var cancellationToken = context.CancellationToken;
var invocationOperation = (IInvocationOperation)context.Operation;
if (invocationOperation.Arguments.Length != 1)
{
return;
}
AnalyzeInvokedMember(
context, infoCache,
invocationOperation.Instance,
invocationOperation.TargetMethod,
invocationOperation.Arguments[0].Value,
lengthLikePropertyOpt: null,
cancellationToken);
}
private void AnalyzePropertyReference(
OperationAnalysisContext context, InfoCache infoCache)
{
var cancellationToken = context.CancellationToken;
var propertyReference = (IPropertyReferenceOperation)context.Operation;
// Only analyze indexer calls.
if (!propertyReference.Property.IsIndexer)
{
return;
}
if (propertyReference.Arguments.Length != 1)
{
return;
}
AnalyzeInvokedMember(
context, infoCache,
propertyReference.Instance,
propertyReference.Property.GetMethod,
propertyReference.Arguments[0].Value,
lengthLikePropertyOpt: null,
cancellationToken);
}
private void AnalyzeArrayElementReference(
OperationAnalysisContext context, InfoCache infoCache, IPropertySymbol arrayLengthProperty)
{
var cancellationToken = context.CancellationToken;
var arrayElementReference = (IArrayElementReferenceOperation)context.Operation;
// Has to be a single-dimensional element access.
if (arrayElementReference.Indices.Length != 1)
{
return;
}
AnalyzeInvokedMember(
context, infoCache,
arrayElementReference.ArrayReference,
targetMethodOpt: null,
arrayElementReference.Indices[0],
lengthLikePropertyOpt: arrayLengthProperty,
cancellationToken);
}
private void AnalyzeInvokedMember(
OperationAnalysisContext context, InfoCache infoCache,
IOperation instance, IMethodSymbol targetMethodOpt, IOperation argumentValue,
IPropertySymbol lengthLikePropertyOpt, CancellationToken cancellationToken)
{
// look for `s[s.Length - value]` or `s.Get(s.Length- value)`.
// Needs to have the one arg for `s.Length - value`, and that arg needs to be
// a subtraction.
if (instance is null ||
!IsSubtraction(argumentValue, out var subtraction))
{
return;
}
if (!(subtraction.Syntax is BinaryExpressionSyntax binaryExpression))
{
return;
}
// Only supported on C# 8 and above.
var syntaxTree = binaryExpression.SyntaxTree;
var parseOptions = (CSharpParseOptions)syntaxTree.Options;
if (parseOptions.LanguageVersion < LanguageVersion.CSharp8)
{
return;
}
// Don't bother analyzing if the user doesn't like using Index/Range operators.
var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();
if (optionSet is null)
{
return;
}
var option = optionSet.GetOption(CSharpCodeStyleOptions.PreferIndexOperator);
if (!option.Value)
{
return;
}
// Ok, looks promising. We're indexing in with some subtraction expression. Examine the
// type this indexer is in to see if there's another member that takes a System.Index
// that we can convert to.
//
// 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.
lengthLikePropertyOpt = lengthLikePropertyOpt ?? TryGetLengthLikeProperty(infoCache, targetMethodOpt);
if (lengthLikePropertyOpt == null ||
!IsInstanceLengthCheck(lengthLikePropertyOpt, instance, subtraction.LeftOperand))
{
return;
}
// Everything looks good. We can update this to use the System.Index member instead.
context.ReportDiagnostic(
DiagnosticHelper.Create(
Descriptor,
binaryExpression.GetLocation(),
option.Notification.Severity,
ImmutableArray<Location>.Empty,
ImmutableDictionary<string, string>.Empty));
}
private IPropertySymbol TryGetLengthLikeProperty(InfoCache infoCache, IMethodSymbol targetMethodOpt)
=> targetMethodOpt != null && infoCache.TryGetMemberInfo(targetMethodOpt, out var memberInfo)
? memberInfo.LengthLikeProperty
: null;
}
}
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
using static CSharpUseRangeOperatorDiagnosticAnalyzer;
using static Helpers;
using static SyntaxFactory;
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
internal class CSharpUseRangeOperatorCodeFixProvider : SyntaxEditorBasedCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(IDEDiagnosticIds.UseRangeOperatorDiagnosticId);
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 async Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
SyntaxEditor editor, CancellationToken cancellationToken)
{
var invocationNodes = diagnostics.Select(d => GetInvocationExpression(d, cancellationToken))
.OrderByDescending(i => i.SpanStart)
.ToImmutableArray();
await editor.ApplyExpressionLevelSemanticEditsAsync(
document, invocationNodes,
canReplace: (_1, _2) => true,
(semanticModel, currentRoot, currentInvocation) =>
UpdateInvocation(semanticModel, currentRoot, currentInvocation, cancellationToken),
cancellationToken);
}
private SyntaxNode UpdateInvocation(
SemanticModel semanticModel, SyntaxNode currentRoot,
InvocationExpressionSyntax currentInvocation,
CancellationToken cancellationToken)
{
var invocation = semanticModel.GetOperation(currentInvocation, cancellationToken) as IInvocationOperation;
if (invocation != null)
{
var infoCache = new InfoCache(semanticModel.Compilation);
var resultOpt = AnalyzeInvocation(
invocation, infoCache, analyzerOptionsOpt: null, cancellationToken);
if (resultOpt != null)
{
var result = resultOpt.Value;
var updatedNode = FixOne(semanticModel, result, cancellationToken);
if (updatedNode != null)
{
return currentRoot.ReplaceNode(result.Invocation, updatedNode);
}
}
}
return currentRoot;
}
private static InvocationExpressionSyntax GetInvocationExpression(Diagnostic d, CancellationToken cancellationToken)
=> (InvocationExpressionSyntax)d.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken);
private ExpressionSyntax FixOne(
SemanticModel semanticModel, Result result, CancellationToken cancellationToken)
{
var invocation = result.Invocation;
var expression = invocation.Expression is MemberAccessExpressionSyntax memberAccess
? memberAccess.Expression
: invocation.Expression;
var rangeExpression = CreateRangeExpression(semanticModel, result, cancellationToken);
var argument = Argument(rangeExpression).WithAdditionalAnnotations(Formatter.Annotation);
var arguments = SingletonSeparatedList(argument);
if (result.MemberInfo.OverloadedMethodOpt == null)
{
var argList = invocation.ArgumentList;
return ElementAccessExpression(
expression,
BracketedArgumentList(
Token(SyntaxKind.OpenBracketToken).WithTriviaFrom(argList.OpenParenToken),
arguments,
Token(SyntaxKind.CloseBracketToken).WithTriviaFrom(argList.CloseParenToken)));
}
else
{
return invocation.ReplaceNode(
invocation.ArgumentList,
invocation.ArgumentList.WithArguments(arguments));
}
}
private RangeExpressionSyntax CreateRangeExpression(
SemanticModel semanticModel, Result result, CancellationToken cancellationToken)
{
switch (result.Kind)
{
case ResultKind.Computed:
return CreateComputedRange(semanticModel, result, cancellationToken);
case ResultKind.Constant:
return CreateConstantRange(semanticModel, result, cancellationToken);
default:
throw ExceptionUtilities.Unreachable;
}
}
private RangeExpressionSyntax CreateComputedRange(
SemanticModel semanticModel, Result result, CancellationToken cancellationToken)
{
// We have enough information now to generate `start..end`. However, this will often
// not be what the user wants. For example, generating `start..expr.Length` is not as
// desirable as `start..`. Similarly, `start..(expr.Length - 1)` is not as desirable as
// `start..^1`.
var startOperation = result.Op1;
var endOperation = result.Op2;
var startExpr = (ExpressionSyntax)startOperation.Syntax;
var endExpr = (ExpressionSyntax)endOperation.Syntax;
var startFromEnd = false;
var endFromEnd = false;
var lengthLikeProperty = result.MemberInfo.LengthLikeProperty;
var instance = result.InvocationOperation.Instance;
// If our start-op is actually equivalent to `expr.Length - val`, then just change our
// start-op to be `val` and record that we should emit it as `^val`.
startFromEnd = IsFromEnd(lengthLikeProperty, instance, ref startOperation);
startExpr = (ExpressionSyntax)startOperation.Syntax;
// Similarly, if our end-op is actually equivalent to `expr.Length - val`, then just
// change our end-op to be `val` and record that we should emit it as `^val`.
endFromEnd = IsFromEnd(lengthLikeProperty, instance, ref endOperation);
endExpr = (ExpressionSyntax)endOperation.Syntax;
// If the range operation goes to 'expr.Length' then we can just leave off the end part
// of the range. i.e. `start..`
if (IsInstanceLengthCheck(lengthLikeProperty, instance, endOperation))
{
endExpr = null;
}
// If we're starting the range operation from 0, then we can just leave off the start of
// the range. i.e. `..end`
if (startOperation.ConstantValue.HasValue &&
startOperation.ConstantValue.Value is 0)
{
startExpr = null;
}
return RangeExpression(
startExpr != null && startFromEnd ? IndexExpression(startExpr) : startExpr,
endExpr != null && endFromEnd ? IndexExpression(endExpr) : endExpr);
}
private static ExpressionSyntax GetExpression(ImmutableDictionary<string, string> props, ExpressionSyntax expr, string omitKey, string fromEndKey)
=> props.ContainsKey(omitKey)
? null
: props.ContainsKey(fromEndKey) ? IndexExpression(expr) : expr;
private static RangeExpressionSyntax CreateConstantRange(
SemanticModel semanticModel, Result result, CancellationToken cancellationToken)
{
var constant1Syntax = (ExpressionSyntax)result.Op1.Syntax;
var constant2Syntax = (ExpressionSyntax)result.Op2.Syntax;
// the form is s.Slice(constant1, s.Length - constant2). Want to generate
// s[constant1..(constant2-constant1)]
var constant1 = GetInt32Value(result.Op1);
var constant2 = GetInt32Value(result.Op2);
var endExpr = (ExpressionSyntax)CSharpSyntaxGenerator.Instance.LiteralExpression(constant2 - constant1);
return RangeExpression(
constant1Syntax,
IndexExpression(endExpr));
}
private static int GetInt32Value(IOperation operation)
=> (int)operation.ConstantValue.Value;
/// <summary>
/// check if its the form: `expr.Length - value`. If so, update rangeOperation to then
/// point to 'value' so that we can generate '^value'.
/// </summary>
private static bool IsFromEnd(
IPropertySymbol lengthLikeProperty, IOperation instance, ref IOperation rangeOperation)
{
if (IsSubtraction(rangeOperation, out var subtraction) &&
IsInstanceLengthCheck(lengthLikeProperty, instance, subtraction.LeftOperand))
{
rangeOperation = subtraction.RightOperand;
return true;
}
return false;
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Use_range_operator, createChangedDocument, FeaturesResources.Use_range_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.Concurrent;
using System.Linq;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
using System.Diagnostics;
using static Helpers;
internal partial class CSharpUseRangeOperatorDiagnosticAnalyzer
{
/// <summary>
/// Helper type to cache information about types while analyzing the compilation.
/// </summary>
public class InfoCache
{
/// <summary>
/// The System.Range type. Needed so that we only fixup code if we see the type
/// we're using has an indexer that takes a Range.
/// </summary>
private readonly INamedTypeSymbol _rangeType;
private readonly ConcurrentDictionary<IMethodSymbol, MemberInfo> _methodToMemberInfo;
public InfoCache(Compilation compilation)
{
_rangeType = compilation.GetTypeByMetadataName("System.Range");
_methodToMemberInfo = new ConcurrentDictionary<IMethodSymbol, MemberInfo>();
// Always allow using System.Range indexers with System.String.Substring. The
// compiler has hard-coded knowledge on how to use this type, even if there is no
// this[Range] indexer declared on it directly.
var stringType = compilation.GetSpecialType(SpecialType.System_String);
var substringMethod = stringType.GetMembers(nameof(string.Substring))
.OfType<IMethodSymbol>()
.FirstOrDefault(m => IsSliceLikeMethod(m));
_methodToMemberInfo[substringMethod] = ComputeMemberInfo(substringMethod, requireRangeMember: false);
}
private IMethodSymbol GetSliceLikeMethod(INamedTypeSymbol namedType)
=> namedType.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => IsSliceLikeMethod(m))
.FirstOrDefault();
public bool TryGetMemberInfo(IMethodSymbol method, out MemberInfo memberInfo)
{
if (!IsSliceLikeMethod(method))
{
memberInfo = default;
return false;
}
memberInfo = _methodToMemberInfo.GetOrAdd(method, m => ComputeMemberInfo(m, requireRangeMember: true));
return memberInfo.LengthLikeProperty != null;
}
private MemberInfo ComputeMemberInfo(IMethodSymbol sliceLikeMethod, bool requireRangeMember)
{
Debug.Assert(IsSliceLikeMethod(sliceLikeMethod));
// Check that the type has an int32 'Length' or 'Count' property. If not, we don't
// consider it something indexable.
var containingType = sliceLikeMethod.ContainingType;
var lengthLikeProperty = TryGetLengthOrCountProperty(containingType);
if (lengthLikeProperty == null)
{
return default;
}
if (!requireRangeMember)
{
return new MemberInfo(lengthLikeProperty, overloadedMethodOpt: null);
}
// A Slice method can either be paired with an Range-taking indexer on the type, or
// an Range-taking overload.
if (sliceLikeMethod.ReturnType.Equals(containingType))
{
// it's a method like: MyType MyType.Slice(int start, int length). Look for an
// indexer like `MyType MyType.this[Range range]`. If we can't find one return
// 'default' so we'll consider this named-type non-viable.
var indexer = GetIndexer(containingType, _rangeType, containingType);
if (indexer != null)
{
return new MemberInfo(lengthLikeProperty, overloadedMethodOpt: null);
}
}
// it's a method like: `SomeType MyType.Slice(int start, int length)`. Look
// for an overload like: `SomeType MyType.Slice(Range)`
var overloadedRangeMethod = GetOverload(sliceLikeMethod, _rangeType);
if (overloadedRangeMethod != null)
{
return new MemberInfo(lengthLikeProperty, overloadedRangeMethod);
}
// A slice-like method that we can't convert.
return default;
}
}
}
}
// 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.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
internal partial class CSharpUseRangeOperatorDiagnosticAnalyzer
{
public enum ResultKind
{
// like s.Substring(expr, s.Length - expr). 'expr' has to match on both sides.
Computed,
// like s.Substring(constant1, s.Length - constant2). the constants don't have to match.
Constant,
}
public readonly struct Result
{
public readonly ResultKind Kind;
public readonly CodeStyleOption<bool> Option;
public readonly IInvocationOperation InvocationOperation;
public readonly InvocationExpressionSyntax Invocation;
public readonly IMethodSymbol SliceLikeMethod;
public readonly MemberInfo MemberInfo;
public readonly IOperation Op1;
public readonly IOperation Op2;
public Result(
ResultKind kind, CodeStyleOption<bool> option,
IInvocationOperation invocationOperation, InvocationExpressionSyntax invocation,
IMethodSymbol sliceLikeMethod, MemberInfo memberInfo,
IOperation op1, IOperation op2)
{
Kind = kind;
Option = option;
InvocationOperation = invocationOperation;
Invocation = invocation;
SliceLikeMethod = sliceLikeMethod;
MemberInfo = memberInfo;
Op1 = op1;
Op2 = op2;
}
}
}
}
// 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.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
using static Helpers;
/// <summary>
/// Analyzer that looks for several variants of code like `s.Slice(start, end - start)` and
/// offers to update to `s[start..end]` or `s.Slice(start..end)`. In order to convert to the
/// indexer, the type being called on needs a slice-like method that takes two ints, and returns
/// an instance of the same type. It also needs a Length/Count property, as well as an indexer
/// that takes a System.Range instance. In order to convert between methods, there need to be
/// two overloads that are equivalent except that one takes two ints, and the other takes a
/// System.Range.
///
/// It is assumed that if the type follows this shape that it is well behaved and that this
/// transformation will preserve semantics. If this assumption is not good in practice, we
/// could always limit the feature to only work on a whitelist of known safe types.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp), Shared]
internal partial class CSharpUseRangeOperatorDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
// public const string UseIndexer = nameof(UseIndexer);
public const string ComputedRange = nameof(ComputedRange);
public const string ConstantRange = nameof(ConstantRange);
public CSharpUseRangeOperatorDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseRangeOperatorDiagnosticId,
new LocalizableResourceString(nameof(FeaturesResources.Use_range_operator), FeaturesResources.ResourceManager, typeof(FeaturesResources)),
new LocalizableResourceString(nameof(FeaturesResources._0_can_be_simplified), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
{
}
public override bool OpenFileOnly(Workspace workspace) => false;
public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
protected override void InitializeWorker(AnalysisContext context)
{
context.RegisterCompilationStartAction(compilationContext =>
{
// We're going to be checking every invocation in the compilation. Cache information
// we compute in this object so we don't have to continually recompute it.
var infoCache = new InfoCache(compilationContext.Compilation);
compilationContext.RegisterOperationAction(
c => AnalyzeInvocation(c, infoCache),
OperationKind.Invocation);
});
}
private void AnalyzeInvocation(
OperationAnalysisContext context, InfoCache infoCache)
{
var resultOpt = AnalyzeInvocation(
(IInvocationOperation)context.Operation, infoCache, context.Options, context.CancellationToken);
if (resultOpt == null)
{
return;
}
context.ReportDiagnostic(CreateDiagnostic(resultOpt.Value));
}
public static Result? AnalyzeInvocation(
IInvocationOperation invocation, InfoCache infoCache,
AnalyzerOptions analyzerOptionsOpt, CancellationToken cancellationToken)
{
// Validate we're on a piece of syntax we expect. While not necessary for analysis, we
// want to make sure we're on something the fixer will know how to actually fix.
var invocationSyntax = invocation.Syntax as InvocationExpressionSyntax;
if (invocationSyntax is null ||
invocationSyntax.ArgumentList is null)
{
return default;
}
CodeStyleOption<bool> option = null;
if (analyzerOptionsOpt != null)
{
// Check if we're at least on C# 8, and that the user wants these operators.
var syntaxTree = invocationSyntax.SyntaxTree;
var parseOptions = (CSharpParseOptions)syntaxTree.Options;
if (parseOptions.LanguageVersion < LanguageVersion.CSharp8)
{
return default;
}
var optionSet = analyzerOptionsOpt.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();
if (optionSet is null)
{
return default;
}
option = optionSet.GetOption(CSharpCodeStyleOptions.PreferRangeOperator);
if (!option.Value)
{
return default;
}
}
// look for `s.Slice(e1, end - e2)`
if (invocation.Instance is null ||
invocation.Arguments.Length != 2)
{
return default;
}
// See if the call is to something slice-like.
var targetMethod = invocation.TargetMethod;
// Second arg needs to be a subtraction for: `end - e2`. Once we've seen that we have
// that, try to see if we're calling into some sort of Slice method with a matching
// indexer or overload
if (!IsSubtraction(invocation.Arguments[1].Value, out var subtraction) ||
!infoCache.TryGetMemberInfo(targetMethod, out var memberInfo))
{
return default;
}
// See if we have: (start, end - start). Specifically where the start operation it the
// same as the right side of the subtraction.
var startOperation = invocation.Arguments[0].Value;
if (CSharpSyntaxFactsService.Instance.AreEquivalent(startOperation.Syntax, subtraction.RightOperand.Syntax))
{
return new Result(
ResultKind.Computed, option,
invocation, invocationSyntax,
targetMethod, memberInfo,
startOperation, subtraction.LeftOperand);
}
// See if we have: (constant1, s.Length - constant2). The constants don't have to be
// the same value. This will convert over to s[constant1..(constant - constant1)]
if (IsConstantInt32(startOperation) &&
IsConstantInt32(subtraction.RightOperand) &&
IsInstanceLengthCheck(memberInfo.LengthLikeProperty, invocation.Instance, subtraction.LeftOperand))
{
return new Result(
ResultKind.Constant, option,
invocation, invocationSyntax,
targetMethod, memberInfo,
startOperation, subtraction.RightOperand);
}
return default;
}
private Diagnostic CreateDiagnostic(Result result)
{
// Keep track of the invocation node
var invocation = result.Invocation;
var additionalLocations = ImmutableArray.Create(
invocation.GetLocation());
// Mark the span under the two arguments to .Slice(..., ...) as what we will be
// updating.
var arguments = invocation.ArgumentList.Arguments;
var location = Location.Create(invocation.SyntaxTree,
TextSpan.FromBounds(arguments.First().SpanStart, arguments.Last().Span.End));
return DiagnosticHelper.Create(
Descriptor,
location,
result.Option.Notification.Severity,
additionalLocations,
ImmutableDictionary<string, string>.Empty,
result.SliceLikeMethod.Name);
}
private static bool IsConstantInt32(IOperation operation)
=> operation.ConstantValue.HasValue && operation.ConstantValue.Value is int;
}
}
// 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;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
internal static class Helpers
{
/// <summary>
/// Find an `int MyType.Count` or `int MyType.Length` property.
/// </summary>
public static IPropertySymbol TryGetLengthOrCountProperty(ITypeSymbol namedType)
=> TryGetNoArgInt32Property(namedType, nameof(string.Length)) ??
TryGetNoArgInt32Property(namedType, nameof(ICollection.Count));
/// <summary>
/// Tried to find a public, non-static, int-returning property in the given type with the
/// specified <paramref name="name"/>.
/// </summary>
public static IPropertySymbol TryGetNoArgInt32Property(ITypeSymbol type, string name)
=> type.GetMembers(name)
.OfType<IPropertySymbol>()
.Where(p => IsPublicInstance(p) &&
p.Type.SpecialType == SpecialType.System_Int32)
.FirstOrDefault();
public static bool IsPublicInstance(ISymbol symbol)
=> !symbol.IsStatic && symbol.DeclaredAccessibility == Accessibility.Public;
/// <summary>
/// Creates an `^expr` index expression from a given `expr`.
/// </summary>
public static PrefixUnaryExpressionSyntax IndexExpression(ExpressionSyntax expr)
=> SyntaxFactory.PrefixUnaryExpression(
SyntaxKind.IndexExpression,
expr.Parenthesize());
/// <summary>
/// Checks if this <paramref name="operation"/> is `expr.Length` where `expr` is equivalent
/// to the <paramref name="instance"/> we were originally invoking an accessor/method off
/// of.
/// </summary>
public static bool IsInstanceLengthCheck(IPropertySymbol lengthLikeProperty, IOperation instance, IOperation operation)
=> operation is IPropertyReferenceOperation propertyRef &&
propertyRef.Instance != null &&
lengthLikeProperty.Equals(propertyRef.Property) &&
CSharpSyntaxFactsService.Instance.AreEquivalent(instance.Syntax, propertyRef.Instance.Syntax);
/// <summary>
/// Checks if <paramref name="operation"/> is a binary subtraction operator. If so, it
/// will be returned through <paramref name="subtraction"/>.
/// </summary>
public static bool IsSubtraction(IOperation operation, out IBinaryOperation subtraction)
{
if (operation is IBinaryOperation binaryOperation &&
binaryOperation.OperatorKind == BinaryOperatorKind.Subtract)
{
subtraction = binaryOperation;
return true;
}
subtraction = null;
return false;
}
private static bool IsConstantInt32(IOperation operation)
=> operation.ConstantValue.HasValue && operation.ConstantValue.Value is int;
/// <summary>
/// Look for methods like "SomeType MyType.Get(int)". Also matches against the 'getter'
/// of an indexer like 'SomeType MyType.this[int]`
/// </summary>
public static bool IsIntIndexingMethod(IMethodSymbol method)
=> method != null &&
(method.MethodKind == MethodKind.PropertyGet || method.MethodKind == MethodKind.Ordinary) &&
IsPublicInstance(method) &&
method.Parameters.Length == 1 &&
method.Parameters[0].Type.SpecialType == SpecialType.System_Int32;
/// <summary>
/// Look for methods like "SomeType MyType.Slice(int start, int length)". Note that the
/// names of the parameters are checked to ensure they are appropriate slice-like. These
/// names were picked by examining the patterns in the BCL for slicing members.
/// </summary>
public static bool IsSliceLikeMethod(IMethodSymbol method)
=> method != null &&
IsPublicInstance(method) &&
method.Parameters.Length == 2 &&
IsSliceFirstParameter(method.Parameters[0]) &&
IsSliceSecondParameter(method.Parameters[1]);
private static bool IsSliceFirstParameter(IParameterSymbol parameter)
=> parameter.Type.SpecialType == SpecialType.System_Int32 &&
(parameter.Name == "start" || parameter.Name == "startIndex");
private static bool IsSliceSecondParameter(IParameterSymbol parameter)
=> parameter.Type.SpecialType == SpecialType.System_Int32 &&
(parameter.Name == "count" || parameter.Name == "length");
/// <summary>
/// Finds a public, non-static indexer in the given type. The indexer has to accept the
/// provided <paramref name="parameterType"/> and must return the provided <paramref
/// name="returnType"/>.
/// </summary>
public static IPropertySymbol GetIndexer(ITypeSymbol type, ITypeSymbol parameterType, ITypeSymbol returnType)
=> type.GetMembers(WellKnownMemberNames.Indexer)
.OfType<IPropertySymbol>()
.Where(p => p.IsIndexer &&
IsPublicInstance(p) &&
returnType.Equals(p.Type) &&
p.Parameters.Length == 1 &&
p.Parameters[0].Type.Equals(parameterType))
.FirstOrDefault();
/// <summary>
/// Finds a public, non-static overload of <paramref name="method"/> in the containing type.
/// The overload must have the same return type as <paramref name="method"/>. It must only
/// have a single parameter, with the provided <paramref name="parameterType"/>.
/// </summary>
public static IMethodSymbol GetOverload(IMethodSymbol method, ITypeSymbol parameterType)
=> method.MethodKind != MethodKind.Ordinary
? null
: method.ContainingType.GetMembers(method.Name)
.OfType<IMethodSymbol>()
.Where(m => IsPublicInstance(m) &&
m.Parameters.Length == 1 &&
m.Parameters[0].Type.Equals(parameterType) &&
m.ReturnType.Equals(method.ReturnType))
.FirstOrDefault();
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
internal readonly struct MemberInfo
{
/// <summary>
/// The Length/Count property on the type. Must be public, non-static, no-parameter,
/// int32 returning.
/// </summary>
public readonly IPropertySymbol LengthLikeProperty;
/// <summary>
/// Optional paired overload that takes a Range/Index parameter instead.
/// </summary>
public readonly IMethodSymbol OverloadedMethodOpt;
public MemberInfo(
IPropertySymbol lengthLikeProperty,
IMethodSymbol overloadedMethodOpt)
{
LengthLikeProperty = lengthLikeProperty;
OverloadedMethodOpt = overloadedMethodOpt;
}
}
}
// 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.Reflection;
namespace Microsoft.CodeAnalysis.Diagnostics
{
internal static class IDEDiagnosticIds
......@@ -88,6 +92,9 @@ internal static class IDEDiagnosticIds
public const string FormattingDiagnosticId = "IDE0055";
public const string UseIndexOperatorDiagnosticId = "IDE0056";
public const string UseRangeOperatorDiagnosticId = "IDE0057";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
public const string AnalyzerDependencyConflictId = "IDE1002";
......
......@@ -70,6 +70,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to {0} can be simplified.
/// </summary>
internal static string _0_can_be_simplified {
get {
return ResourceManager.GetString("_0_can_be_simplified", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} Keyword.
/// </summary>
......@@ -1855,6 +1864,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 +3951,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>
......@@ -3997,6 +4024,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Use range operator.
/// </summary>
internal static string Use_range_operator {
get {
return ResourceManager.GetString("Use_range_operator", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use &apos;throw&apos; expression.
/// </summary>
......
......@@ -1469,4 +1469,16 @@ 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>
<data name="Use_range_operator" xml:space="preserve">
<value>Use range operator</value>
</data>
<data name="_0_can_be_simplified" xml:space="preserve">
<value>{0} can be simplified</value>
</data>
</root>
\ No newline at end of file
......@@ -87,6 +87,11 @@
<target state="translated">Formátuje se dokument.</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">obecné přetížení</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">Dokument wird formatiert</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">generische Überladung</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">Aplicando formato al documento</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">sobrecarga genérica</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">Mise en forme du document</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">surcharge générique</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">Formattazione del documento</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">overload generico</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">ドキュメントの書式設定</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">ジェネリック オーバーロード</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">문서 서식 지정</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">제네릭 오버로드</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">Formatowanie dokumentu</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">przeciążenie ogólne</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">Formatando documento</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">sobrecarga genérica</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">Форматирование документа</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">универсальная перегрузка</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">Belge biçimlendiriliyor</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">genel aşırı yükleme</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">设置文档格式</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">泛型重载</target>
......
......@@ -87,6 +87,11 @@
<target state="translated">正在將文件格式化</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target>
<note />
</trans-unit>
<trans-unit id="Introduce_constant">
<source>Introduce constant</source>
<target state="new">Introduce constant</target>
......@@ -167,11 +172,26 @@
<target state="new">Use expression body for lambda expressions</target>
<note />
</trans-unit>
<trans-unit id="Use_index_operator">
<source>Use index operator</source>
<target state="new">Use index operator</target>
<note />
</trans-unit>
<trans-unit id="Use_interpolated_verbatim_string">
<source>Use interpolated verbatim string</source>
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator">
<source>Use range operator</source>
<target state="new">Use range operator</target>
<note />
</trans-unit>
<trans-unit id="_0_can_be_simplified">
<source>{0} can be simplified</source>
<target state="new">{0} can be simplified</target>
<note />
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">泛型多載</target>
......
......@@ -130,6 +130,7 @@ public static class Features
public const string CodeActionsUseConditionalExpression = "CodeActions.UseConditionalExpression";
public const string CodeActionsUseDeconstruction = "CodeActions.UseDeconstruction";
public const string CodeActionsUseDefaultLiteral = "CodeActions.UseDefaultLiteral";
public const string CodeActionsUseIndexOperator = "CodeActions.UseIndexOperator";
public const string CodeActionsUseInferredMemberName = "CodeActions.UseInferredMemberName";
public const string CodeActionsUseExpressionBody = "CodeActions.UseExpressionBody";
public const string CodeActionsUseImplicitType = "CodeActions.UseImplicitType";
......@@ -142,6 +143,7 @@ public static class Features
public const string CodeActionsUseNullPropagation = "CodeActions.UseNullPropagation";
public const string CodeActionsUseNamedArguments = "CodeActions.UseNamedArguments";
public const string CodeActionsUseObjectInitializer = "CodeActions.UseObjectInitializer";
public const string CodeActionsUseRangeOperator = "CodeActions.UseRangeOperator";
public const string CodeActionsUseThrowExpression = "CodeActions.UseThrowExpression";
public const string CodeCleanup = nameof(CodeCleanup);
public const string CodeGeneration = nameof(CodeGeneration);
......
......@@ -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
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
......@@ -62,6 +62,16 @@
<target state="new">Prefer compound assignments</target>
<note />
</trans-unit>
<trans-unit id="Prefer_index_operator">
<source>Prefer index operator</source>
<target state="new">Prefer index operator</target>
<note />
</trans-unit>
<trans-unit id="Prefer_range_operator">
<source>Prefer range operator</source>
<target state="new">Prefer range operator</target>
<note />
</trans-unit>
<trans-unit id="Regular_Expressions">
<source>Regular Expressions</source>
<target state="new">Regular Expressions</target>
......
' 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.Text
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeStyle
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
......@@ -9,7 +8,7 @@ Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Roslyn.Test.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.UnitTests
<[UseExportProvider]>
<UseExportProvider>
Public Class CSharpEditorConfigGeneratorTests
Inherits TestBase
......@@ -105,6 +104,8 @@ csharp_prefer_braces = true:silent
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
#### C# Formatting Rules ####
......@@ -254,6 +255,8 @@ csharp_prefer_braces = true:silent
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
#### C# Formatting Rules ####
......
......@@ -61,6 +61,20 @@ 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 Option<CodeStyleOption<bool>> PreferRangeOperator = CreateOption(
CSharpCodeStyleOptionGroups.ExpressionLevelPreferences, nameof(PreferRangeOperator),
defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement,
storageLocations: new OptionStorageLocation[] {
EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_prefer_range_operator"),
new RoamingProfileStorageLocation("TextEditor.CSharp.Specific.PreferRangeOperator")});
public static readonly CodeStyleOption<ExpressionBodyPreference> NeverWithSilentEnforcement =
new CodeStyleOption<ExpressionBodyPreference>(ExpressionBodyPreference.Never, NotificationOption.Silent);
......@@ -205,6 +219,8 @@ public static IEnumerable<Option<CodeStyleOption<bool>>> GetCodeStyleOptions()
yield return PreferBraces;
yield return PreferSimpleDefaultExpression;
yield return PreferLocalOverAnonymousFunction;
yield return PreferIndexOperator;
yield return PreferRangeOperator;
}
public static IEnumerable<Option<CodeStyleOption<ExpressionBodyPreference>>> GetExpressionBodyOptions()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册