提交 6b513abf 编写于 作者: C Cyrus Najmabadi

Add tests.

上级 f95133eb
......@@ -37,7 +37,7 @@ void Goo(string s)
parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7)));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestSimple()
{
await TestAsync(
......@@ -59,7 +59,7 @@ void Goo(string s)
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestComplexSubtaction()
{
await TestAsync(
......@@ -81,7 +81,7 @@ void Goo(string s)
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestComplexInstance()
{
await TestAsync(
......@@ -107,7 +107,7 @@ void Goo(string[] ss)
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNotWithoutSubtraction1()
{
await TestMissingAsync(
......@@ -121,7 +121,7 @@ void Goo(string s)
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNotWithoutSubtraction2()
{
await TestMissingAsync(
......@@ -135,7 +135,7 @@ void Goo(string s)
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestNotWithoutMultipleArgs()
{
await TestMissingAsync(
......@@ -149,7 +149,7 @@ void Goo(string s)
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithLength()
{
await TestAsync(
......@@ -175,7 +175,7 @@ void Goo(S s)
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithCount()
{
await TestAsync(
......@@ -201,7 +201,7 @@ void Goo(S s)
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithNoLengthOrCount()
{
await TestMissingAsync(
......@@ -217,7 +217,7 @@ void Goo(S s)
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithNoInt32Indexer()
{
await TestMissingAsync(
......@@ -233,7 +233,7 @@ void Goo(S s)
}", parameters: s_testParameters);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
public async Task TestUserDefinedTypeWithNoIndexIndexer()
{
await TestMissingAsync(
......
......@@ -22,7 +22,7 @@ internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProvider
private static readonly TestParameters s_testParameters =
new TestParameters(parseOptions: s_parseOptions);
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIndexOperator)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestNotInCSharp7()
{
await TestMissingAsync(
......@@ -37,7 +37,7 @@ void Goo(string s)
parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7)));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestSimple()
{
await TestAsync(
......@@ -59,14 +59,14 @@ void Goo(string s)
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestComplexSubstraction()
{
await TestAsync(
@"
class C
{
void Goo(string s)
void Goo(string s, int bar, int baz)
{
var v = s.Substring([||]bar, s.Length - baz - bar);
}
......@@ -74,14 +74,14 @@ void Goo(string s)
@"
class C
{
void Goo(string s)
void Goo(string s, int bar, int baz)
{
var v = s[bar..^baz];
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestConstantSubtraction1()
{
await TestAsync(
......
......@@ -7,11 +7,14 @@
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.UseIndexOperator
{
......@@ -34,31 +37,29 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
return Task.CompletedTask;
}
protected override Task FixAllAsync(
protected override async Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
SyntaxEditor editor, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
foreach (var diagnostic in diagnostics)
{
FixOne(diagnostic, editor, cancellationToken);
FixOne(semanticModel, editor, diagnostic, cancellationToken);
}
return Task.CompletedTask;
}
private void FixOne(
Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken)
SemanticModel semanticModel, SyntaxEditor editor,
Diagnostic diagnostic, CancellationToken cancellationToken)
{
var invocation = (InvocationExpressionSyntax)diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken);
var expression = invocation.Expression is MemberAccessExpressionSyntax memberAccess
? memberAccess.Expression
: invocation.Expression;
var start = (ExpressionSyntax)diagnostic.AdditionalLocations[1].FindNode(getInnermostNodeForTie: true, cancellationToken);
var end = (ExpressionSyntax)diagnostic.AdditionalLocations[2].FindNode(getInnermostNodeForTie: true, cancellationToken);
var rangeExpression = RangeExpression(
GetExpression(diagnostic.Properties, start, OmitStart, StartFromEnd),
GetExpression(diagnostic.Properties, end, OmitEnd, EndFromEnd));
var rangeExpression = CreateRangeExpression(
semanticModel, diagnostic, invocation, cancellationToken);
var argList = invocation.ArgumentList;
var elementAccess = ElementAccessExpression(
......@@ -71,11 +72,128 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
editor.ReplaceNode(invocation, elementAccess);
}
private RangeExpressionSyntax CreateRangeExpression(
SemanticModel semanticModel, Diagnostic diagnostic,
InvocationExpressionSyntax invocation, CancellationToken cancellationToken)
{
var properties = diagnostic.Properties;
if (properties.ContainsKey(ComputedRange))
{
return CreateComputedRange(semanticModel, diagnostic, invocation, cancellationToken);
}
else if (properties.ContainsKey(ConstantRange))
{
return CreateConstantRange(semanticModel, diagnostic, cancellationToken);
}
else
{
throw ExceptionUtilities.Unreachable;
}
}
private RangeExpressionSyntax CreateComputedRange(
SemanticModel semanticModel, Diagnostic diagnostic,
InvocationExpressionSyntax invocation, CancellationToken cancellationToken)
{
var startExpr = (ExpressionSyntax)diagnostic.AdditionalLocations[1].FindNode(getInnermostNodeForTie: true, cancellationToken);
var endExpr = (ExpressionSyntax)diagnostic.AdditionalLocations[2].FindNode(getInnermostNodeForTie: true, 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 startFromEnd = false;
var endFromEnd = false;
if (semanticModel.GetOperation(invocation, cancellationToken) is IInvocationOperation invocationOp &&
invocationOp.Instance != null)
{
var startOperation = semanticModel.GetOperation(startExpr, cancellationToken);
var endOperation = semanticModel.GetOperation(endExpr, cancellationToken);
var checker = new InfoCache(semanticModel.Compilation);
if (startOperation != null &&
endOperation != null &&
checker.TryGetMemberInfo(invocationOp.TargetMethod.ContainingType, out var memberInfo))
{
var lengthLikeProperty = memberInfo.LengthLikeProperty;
// 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, invocationOp.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, invocationOp.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, invocationOp.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, Diagnostic diagnostic, CancellationToken cancellationToken)
{
var constant1Syntax = (ExpressionSyntax)diagnostic.AdditionalLocations[1].FindNode(getInnermostNodeForTie: true, cancellationToken);
var constant2Syntax = (ExpressionSyntax)diagnostic.AdditionalLocations[2].FindNode(getInnermostNodeForTie: true, cancellationToken);
// the form is s.Slice(constant1, s.Length - constant2). Want to generate
// s[constant1..(constant2-constant1)]
var constant1 = GetInt32Value(semanticModel.GetOperation(constant1Syntax));
var constant2 = GetInt32Value(semanticModel.GetOperation(constant2Syntax));
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 (rangeOperation is IBinaryOperation binaryOperation &&
binaryOperation.OperatorKind == BinaryOperatorKind.Subtract &&
IsInstanceLengthCheck(lengthLikeProperty, instance, binaryOperation.LeftOperand))
{
rangeOperation = binaryOperation.RightOperand;
return true;
}
return false;
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
......
......@@ -12,7 +12,7 @@ internal partial class CSharpUseRangeOperatorDiagnosticAnalyzer
/// <summary>
/// Helper type to cache information about types while analyzing the compilation.
/// </summary>
private class InfoCache
public class InfoCache
{
/// <summary>
/// The System.Range type. Needed so that we only fixup code if we see the type
......
......@@ -4,7 +4,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseIndexOperator
{
internal partial class CSharpUseRangeOperatorDiagnosticAnalyzer
{
private struct MemberInfo
public struct MemberInfo
{
public readonly IPropertySymbol LengthLikeProperty;
public readonly IMethodSymbol SliceLikeMethod;
......
......@@ -26,13 +26,16 @@ namespace Microsoft.CodeAnalysis.CSharp.UseIndexOperator
[DiagnosticAnalyzer(LanguageNames.CSharp), Shared]
internal partial class CSharpUseRangeOperatorDiagnosticAnalyzer : AbstractCodeStyleDiagnosticAnalyzer
{
// Flags to indicate if we should generate 'val' or '^val' for the start or end range values
public const string StartFromEnd = nameof(StartFromEnd);
public const string EndFromEnd = nameof(EndFromEnd);
public const string ComputedRange = nameof(ComputedRange);
public const string ConstantRange = nameof(ConstantRange);
// Flags to indicate if we should just omit the start/end value of the range entirely.
public const string OmitStart = nameof(OmitStart);
public const string OmitEnd = nameof(OmitEnd);
//// Flags to indicate if we should generate 'val' or '^val' for the start or end range values
//public const string StartFromEnd = nameof(StartFromEnd);
//public const string EndFromEnd = nameof(EndFromEnd);
//// Flags to indicate if we should just omit the start/end value of the range entirely.
//public const string OmitStart = nameof(OmitStart);
//public const string OmitEnd = nameof(OmitEnd);
public CSharpUseRangeOperatorDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseRangeOperatorDiagnosticId,
......@@ -138,97 +141,60 @@ protected override void InitializeWorker(AnalysisContext context)
return;
}
// Make sure we have: (start, end - start). The start operation has to be
// the same as the right side of the subtraction.
// See if we have: (start, end - start). The start operation has to be the same as the
// right side of the subtraction.
var startOperation = invocation.Arguments[0].Value;
var endOperation = subtraction.LeftOperand;
if (!CSharpSyntaxFactsService.Instance.AreEquivalent(startOperation.Syntax, subtraction.RightOperand.Syntax))
if (CSharpSyntaxFactsService.Instance.AreEquivalent(startOperation.Syntax, subtraction.RightOperand.Syntax))
{
context.ReportDiagnostic(CreateDiagnostic(
ComputedRange, option, invocationSyntax,
memberInfo, startOperation, subtraction.LeftOperand));
return;
}
// 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`. Look for these patterns and record what we have so we can produce more
// idiomatic results in the fixer.
//
// Note: we could also compute this in the fixer. But it's nice and easy to do here
// given that we already have the options, and it's cheap to do now.
var properties = ImmutableDictionary.CreateBuilder<string, string>();
var lengthLikeProperty = memberInfo.LengthLikeProperty;
// 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`.
if (IsFromEnd(lengthLikeProperty, invocation.Instance, ref startOperation))
// 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))
{
properties.Add(StartFromEnd, StartFromEnd);
}
// 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`.
if (IsFromEnd(lengthLikeProperty, invocation.Instance, ref endOperation))
{
properties.Add(EndFromEnd, EndFromEnd);
}
// 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, invocation.Instance, endOperation))
{
properties.Add(OmitEnd, OmitEnd);
context.ReportDiagnostic(CreateDiagnostic(
ConstantRange, option, invocationSyntax,
memberInfo, startOperation, subtraction.RightOperand));
return;
}
}
// 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)
{
properties.Add(OmitStart, OmitStart);
}
private Diagnostic CreateDiagnostic(
string rangeKind, CodeStyleOption<bool> option, InvocationExpressionSyntax invocation,
MemberInfo memberInfo, IOperation op1, IOperation op2)
{
var properties = ImmutableDictionary<string, string>.Empty.Add(rangeKind, rangeKind);
// Keep track of the syntax nodes from the start/end ops so that we can easily
// generate the range-expression in the fixer.
var additionalLocations = ImmutableArray.Create(
invocationSyntax.GetLocation(),
startOperation.Syntax.GetLocation(),
endOperation.Syntax.GetLocation());
invocation.GetLocation(),
op1.Syntax.GetLocation(),
op2.Syntax.GetLocation());
// Mark the span under the two arguments to .Slice(..., ...) as what we will be
// updating.
var arguments = invocationSyntax.ArgumentList.Arguments;
var location = Location.Create(syntaxTree,
var arguments = invocation.ArgumentList.Arguments;
var location = Location.Create(invocation.SyntaxTree,
TextSpan.FromBounds(arguments.First().SpanStart, arguments.Last().Span.End));
context.ReportDiagnostic(
DiagnosticHelper.Create(
Descriptor,
location,
option.Notification.Severity,
additionalLocations,
properties.ToImmutable(),
memberInfo.SliceLikeMethod.Name));
return DiagnosticHelper.Create(
Descriptor,
location,
option.Notification.Severity,
additionalLocations,
properties,
memberInfo.SliceLikeMethod.Name);
}
/// <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 bool IsFromEnd(
IPropertySymbol lengthLikeProperty, IOperation instance, ref IOperation rangeOperation)
{
if (rangeOperation is IBinaryOperation binaryOperation &&
binaryOperation.OperatorKind == BinaryOperatorKind.Subtract &&
IsInstanceLengthCheck(lengthLikeProperty, instance, binaryOperation.LeftOperand))
{
rangeOperation = binaryOperation.RightOperand;
return true;
}
return false;
}
private static bool IsConstantInt32(IOperation operation)
=> operation.ConstantValue.HasValue && operation.ConstantValue.Value is int;
}
}
......@@ -60,5 +60,8 @@ public static bool IsSubtraction(IArgumentOperation arg, out IBinaryOperation su
subtraction = null;
return false;
}
private static bool IsConstantInt32(IOperation operation)
=> operation.ConstantValue.HasValue && operation.ConstantValue.Value is int;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册