From 79b4c25ccc3f955a5d83ff0089688d3c56e0e970 Mon Sep 17 00:00:00 2001 From: Simon Lindgren Date: Tue, 7 Aug 2012 16:05:37 +0200 Subject: [PATCH] [CodeIssues] ParameterCanBeDemotedIssue: Add Criterions for suitable indexer members and for array types. Also start resolving the method with the new type to check for errors. --- .../ICSharpCode.NRefactory.CSharp.csproj | 2 + .../HasMemberCriterion.cs | 9 +- .../IsArrayTypeCriterion.cs | 43 ++++++ .../ParameterCanBeDemotedIssue.cs | 103 ++++++++++---- .../SupportsIndexingCriterion.cs | 107 +++++++++++++++ .../TypeCriteriaCollector.cs | 83 +++++++++--- .../ParameterCanBeDemotedTests.cs | 128 +++++++++++++++--- .../SupportsIndexingCriterionTests.cs | 94 +++++++++++++ .../ICSharpCode.NRefactory.Tests.csproj | 1 + 9 files changed, 500 insertions(+), 70 deletions(-) create mode 100644 ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/IsArrayTypeCriterion.cs create mode 100644 ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/SupportsIndexingCriterion.cs create mode 100644 ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/ParameterCanBeDemotedIssue/SupportsIndexingCriterionTests.cs diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index 4032c95608..0a8f9376f1 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -431,6 +431,8 @@ + + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/HasMemberCriterion.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/HasMemberCriterion.cs index efcdb14aaf..36aa1d39f5 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/HasMemberCriterion.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/HasMemberCriterion.cs @@ -61,17 +61,14 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring { var implementedInterfaceMembers = member.MemberDefinition.ImplementedInterfaceMembers; if (implementedInterfaceMembers.Any()) { - return acceptableMembers.ContainsAny(implementedInterfaceMembers); + return ContainsAny(acceptableMembers, implementedInterfaceMembers); } else { return acceptableMembers.Contains(member.MemberDefinition); } } - } - - static class IEnumerableExtensions - { - public static bool ContainsAny(this IEnumerable collection, IEnumerable items) + + static bool ContainsAny(IEnumerable collection, IEnumerable items) { foreach (var item in items) { if (collection.Contains(item)) diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/IsArrayTypeCriterion.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/IsArrayTypeCriterion.cs new file mode 100644 index 0000000000..db011b3560 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/IsArrayTypeCriterion.cs @@ -0,0 +1,43 @@ +// +// IsArrayTypeCriterion.cs +// +// Author: +// Simon Lindgren +// +// Copyright (c) 2012 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + public class IsArrayTypeCriterion : ITypeCriterion + { + #region ITypeCriterion implementation + + public bool SatisfiedBy(IType type) + { + return type is ArrayType; + } + + #endregion + } +} + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/ParameterCanBeDemotedIssue.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/ParameterCanBeDemotedIssue.cs index b64a87c446..367e76b8e9 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/ParameterCanBeDemotedIssue.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/ParameterCanBeDemotedIssue.cs @@ -28,6 +28,9 @@ using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.Semantics; using System.Linq; using System; +using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.TypeSystem.Implementation; +using System.Diagnostics; namespace ICSharpCode.NRefactory.CSharp.Refactoring { @@ -40,7 +43,13 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring #region ICodeIssueProvider implementation public IEnumerable GetIssues(BaseRefactoringContext context) { - return new GatherVisitor(context, this).GetIssues(); + var sw = new Stopwatch(); + sw.Start(); + var gatherer = new GatherVisitor(context, this); + var issues = gatherer.GetIssues().ToList(); + sw.Stop(); + Console.WriteLine("Elapsed time for ParameterCanBeDemotedIssue: {0} (resolved {2} method bodies in file '{1}')", sw.Elapsed, context.ParsedFile.FileName, gatherer.MethodResolveCount); + return issues; } #endregion @@ -68,41 +77,62 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring methodDeclaration.AcceptVisitor(collector); foreach (var parameter in methodDeclaration.Parameters) { - var localResolveResult = context.Resolve(parameter) as LocalResolveResult; - var variable = localResolveResult.Variable; - var currentType = localResolveResult.Type; - var candidateTypes = localResolveResult.Type.GetAllBaseTypes(); - var typeCriterion = collector.GetCriterion(variable); - if (typeCriterion == null) - // No usages in body - return; - var possibleTypes = GetPossibleTypes(candidateTypes, typeCriterion); - var suggestedTypes = possibleTypes.Where(t => t != currentType); - if (suggestedTypes.Any()) { - AddIssue(parameter, context.TranslateString("Parameter can be demoted to base class"), - GetActions(parameter, suggestedTypes)); - } + ProcessParameter(parameter, methodDeclaration.Body, collector); } } + void ProcessParameter(ParameterDeclaration parameter, BlockStatement body, TypeCriteriaCollector collector) + { + var directionExpression = parameter.Parent as DirectionExpression; + if (directionExpression != null && directionExpression.FieldDirection != FieldDirection.None) + // That kind of dependency is out of our control. Better not mess with it. + return; + + var localResolveResult = context.Resolve(parameter) as LocalResolveResult; + var currentType = localResolveResult.Type; + var candidateTypes = localResolveResult.Type.GetAllBaseTypes(); + var criterion = collector.GetCriterion(localResolveResult.Variable); + if (criterion == null) + // No usages in body + return; + + var possibleTypes = + from type in candidateTypes + where criterion.SatisfiedBy(type) && TypeChangeResolvesCorrectly(parameter, body, type) + orderby GetInheritanceDepth(type) ascending + select type; + + var suggestedTypes = possibleTypes.Where(type => !type.Equals(currentType)); + if (suggestedTypes.Any()) { + AddIssue(parameter, context.TranslateString("Parameter can be demoted to base class"), GetActions(parameter, suggestedTypes)); + } + } + + internal int MethodResolveCount = 0; + + bool TypeChangeResolvesCorrectly(ParameterDeclaration parameter, BlockStatement body, IType type) + { + MethodResolveCount++; + var resolver = context.GetResolverStateBefore(body); + resolver.AddVariable(new DefaultParameter(type, parameter.Name)); + var astResolver = new CSharpAstResolver(resolver, body, context.ParsedFile); + var validator = new TypeChangeValidationNavigator(); + astResolver.ApplyNavigator(validator, context.CancellationToken); + return !validator.FoundErrors; + } + IEnumerable GetActions(ParameterDeclaration parameter, IEnumerable possibleTypes) { + var csResolver = context.Resolver.GetResolverStateBefore(parameter); + var astBuilder = new TypeSystemAstBuilder(csResolver); foreach (var type in possibleTypes) { - var typeName = type.Name; - var message = string.Format("{0} '{1}'", context.TranslateString("Demote parameter to "), typeName); + var localType = type; + var message = string.Format(context.TranslateString("Demote parameter to '{0}'"), type.FullName); yield return new CodeAction(message, script => { - script.Replace(parameter.Type, new SimpleType(typeName)); + script.Replace(parameter.Type, astBuilder.ConvertType(localType)); }); } } - - IEnumerable GetPossibleTypes(IEnumerable types, ITypeCriterion criterion) - { - return from type in types - where criterion.SatisfiedBy(type) - orderby GetInheritanceDepth(type) ascending - select type; - } int GetInheritanceDepth(IType declaringType) { @@ -114,6 +144,27 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring return depth; } } + + class TypeChangeValidationNavigator : IResolveVisitorNavigator + { + public bool FoundErrors { get; private set; } + + #region IResolveVisitorNavigator implementation + public ResolveVisitorNavigationMode Scan(AstNode node) + { + return ResolveVisitorNavigationMode.Resolve; + } + public void Resolved(AstNode node, ResolveResult result) + { + FoundErrors |= result.IsError; + } + public void ProcessConversion(Expression expression, ResolveResult result, Conversion conversion, IType targetType) + { + // no-op + } + #endregion + + } } } diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/SupportsIndexingCriterion.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/SupportsIndexingCriterion.cs new file mode 100644 index 0000000000..6b0aeef44d --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/SupportsIndexingCriterion.cs @@ -0,0 +1,107 @@ +// +// SupportsIndexingCriterion.cs +// +// Author: +// Simon Lindgren +// +// Copyright (c) 2012 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using ICSharpCode.NRefactory.TypeSystem; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem.Implementation; +using System.Linq; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.CSharp.Resolver; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + public class SupportsIndexingCriterion : ITypeCriterion + { + IType returnType; + + IList argumentTypes; + + CSharpConversions conversions; + + bool isWriteAccess; + + public SupportsIndexingCriterion(IType returnType, IEnumerable argumentTypes, CSharpConversions conversions, bool isWriteAccess = false) + { + if (returnType == null) + throw new ArgumentNullException("returnType"); + if (argumentTypes == null) + throw new ArgumentNullException("argumentTypes"); + if (conversions == null) + throw new ArgumentNullException("conversions"); + + this.returnType = returnType; + this.argumentTypes = argumentTypes.ToList(); + this.conversions = conversions; + this.isWriteAccess = isWriteAccess; + } + + #region ITypeCriterion implementation + + public bool SatisfiedBy(IType type) + { + var accessors = type.GetAccessors().ToList(); + return accessors.Any(member => { + var parameterizedMember = member as IParameterizedMember; + if (parameterizedMember == null) + return false; + + if (isWriteAccess) { + var parameterCount = member.Parameters.Count; + if (member.Name != "set_Item" || parameterCount < 2) + return false; + var indexerElementType = parameterizedMember.Parameters.Last().Type; + var indexerParameterTypes = parameterizedMember.Parameters.Take(parameterCount - 1).Select(p => p.Type).ToList(); + return IsSignatureMatch(indexerElementType, indexerParameterTypes); + } else { + if (member.Name != "get_Item" || member.Parameters.Count < 1) + return false; + var indexerElementType = parameterizedMember.ReturnType; + var indexerParameterTypes = parameterizedMember.Parameters.Select(p => p.Type).ToList(); + return IsSignatureMatch(indexerElementType, indexerParameterTypes); + } + }); + } + + #endregion + + bool IsSignatureMatch(IType indexerElementType, IList indexerParameterTypes) + { + indexerElementType.GetAllBaseTypes(); + if (indexerParameterTypes.Count != argumentTypes.Count) + return false; + var returnConversion = conversions.ImplicitConversion(indexerElementType, returnType); + if (!returnConversion.IsValid) + return false; + for (int i = 0; i < argumentTypes.Count; i++) { + var conversion = conversions.ImplicitConversion(indexerParameterTypes[i], argumentTypes[i]); + if (!conversion.IsValid) + return false; + } + return true; + } + } +} + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/TypeCriteriaCollector.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/TypeCriteriaCollector.cs index b7cff61dca..11eab29acb 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/TypeCriteriaCollector.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/ParameterCanBeDemotedIssue/TypeCriteriaCollector.cs @@ -27,6 +27,7 @@ using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.Semantics; using System.Collections.Generic; using System.Linq; +using ICSharpCode.NRefactory.CSharp.Resolver; namespace ICSharpCode.NRefactory.CSharp.Refactoring { @@ -60,14 +61,52 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring { base.VisitMemberReferenceExpression(memberReferenceExpression); - var memberResolveResult = context.Resolve(memberReferenceExpression) as MemberResolveResult; - if (memberResolveResult == null) - return; - var targetResolveResult = memberResolveResult.TargetResult as LocalResolveResult; + var targetResolveResult = context.Resolve(memberReferenceExpression.Target) as LocalResolveResult; if (targetResolveResult == null) return; var variable = targetResolveResult.Variable; - AddCriterion(variable, new HasMemberCriterion(memberResolveResult.Member)); + var conversion = context.GetConversion(memberReferenceExpression); + if (conversion.IsMethodGroupConversion) { + AddCriterion(variable, new HasMemberCriterion(conversion.Method)); + } else { + var resolveResult = context.Resolve(memberReferenceExpression); + var memberResolveResult = resolveResult as MemberResolveResult; + if (memberResolveResult != null) + AddCriterion(variable, new HasMemberCriterion(memberResolveResult.Member)); + } + } + + public override void VisitIndexerExpression(IndexerExpression indexerExpression) + { + base.VisitIndexerExpression(indexerExpression); + + var localResolveResult = context.Resolve(indexerExpression.Target) as LocalResolveResult; + if (localResolveResult == null) + return; + var resolveResult = context.Resolve(indexerExpression); + if (localResolveResult == null) + return; + var parent = indexerExpression.Parent; + while (parent is ParenthesizedExpression) + parent = parent.Parent; + if (parent is DirectionExpression) { + // The only types which are indexable and where the indexing expression + // results in a variable is an actual array type + AddCriterion(localResolveResult.Variable, new IsArrayTypeCriterion()); + } else if (resolveResult is ArrayAccessResolveResult) { + var arrayResolveResult = (ArrayAccessResolveResult)resolveResult; + var parameterTypes = arrayResolveResult.Indexes.Select(index => index.Type); + var arrayType = arrayResolveResult.Array.Type as ArrayType; + if (arrayType == null) + return; + var criterion = new SupportsIndexingCriterion(arrayType.ElementType, parameterTypes, CSharpConversions.Get(context.Compilation)); + AddCriterion(localResolveResult.Variable, criterion); + } else if (resolveResult is CSharpInvocationResolveResult) { + var invocationResolveResult = (CSharpInvocationResolveResult)resolveResult; + var parameterTypes = invocationResolveResult.Arguments.Select(arg => arg.Type); + var criterion = new SupportsIndexingCriterion(invocationResolveResult.Member.ReturnType, parameterTypes, CSharpConversions.Get(context.Compilation)); + AddCriterion(localResolveResult.Variable, criterion); + } } public override void VisitInvocationExpression(InvocationExpression invocationExpression) @@ -78,27 +117,35 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring var invocationResolveResult = resolveResult as InvocationResolveResult; if (invocationResolveResult == null) return; - CollectRestrictionsFromNodes(invocationExpression.Arguments); // invocationExpression.Target resolves to a method group and VisitMemberReferenceExpression // only handles members, so handle method groups here var targetResolveResult = invocationResolveResult.TargetResult as LocalResolveResult; - if (targetResolveResult == null) - return; - var variable = targetResolveResult.Variable; - AddCriterion(variable, new HasMemberCriterion(invocationResolveResult.Member)); + if (targetResolveResult != null) { + var variable = targetResolveResult.Variable; + AddCriterion(variable, new HasMemberCriterion(invocationResolveResult.Member)); + } } - void CollectRestrictionsFromNodes(IEnumerable expressions) + protected override void VisitChildren(AstNode node) { - foreach (var expression in expressions) { - var resolveResult = context.Resolve(expression); - var argumentResolveResult = resolveResult as LocalResolveResult; - if (argumentResolveResult == null) - continue; - var expectedType = context.GetExpectedType(expression); - AddCriterion(argumentResolveResult.Variable, new IsTypeCriterion(expectedType)); + if (node is Expression) { + CollectRestrictionsFromNodes((Expression)node); } + base.VisitChildren(node); + } + + void CollectRestrictionsFromNodes(Expression expression) + { + var role = expression.Role; + if (role != Roles.Expression && role != Roles.Argument) + return; + var resolveResult = context.Resolve(expression); + var localResolveResult = resolveResult as LocalResolveResult; + if (localResolveResult == null) + return; + var expectedType = context.GetExpectedType(expression); + AddCriterion(localResolveResult.Variable, new IsTypeCriterion(expectedType)); } class ConjunctionCriteria : ITypeCriterion diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/ParameterCanBeDemotedIssue/ParameterCanBeDemotedTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/ParameterCanBeDemotedIssue/ParameterCanBeDemotedTests.cs index f18e35b91e..6d6d9041b5 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/ParameterCanBeDemotedIssue/ParameterCanBeDemotedTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/ParameterCanBeDemotedIssue/ParameterCanBeDemotedTests.cs @@ -366,50 +366,138 @@ class Test } [Test] - public void RespectsOutgoingCallsTypeRestrictionsWhenPassingResultFromMember() + public void AccountsForNonInvocationMethodGroupUsageInMethodCall() { var input = @" -interface IA +delegate void FooDelegate (string s); +interface IBase { - void Foo(string s); - string Property { get; } + void Bar(); } -class B : IA +interface IDerived : IBase { - public virtual void Foo(string s) {} - public string Property { get; } + void Foo(string s); } class TestClass { - public void F(B b) + public void Bar (IDerived derived) + { + derived.Bar(); + Baz (derived.Foo); + } + + void Baz (FooDelegate fd) { - b.Foo(b.Property); } }"; TestRefactoringContext context; var issues = GetIssues(new ParameterCanBeDemotedIssue(), input, out context); - Assert.AreEqual(1, issues.Count); - var issue = issues [0]; - Assert.AreEqual(1, issue.Actions.Count); - CheckFix(context, issues [0], @" -interface IA + Assert.AreEqual(0, issues.Count); + } + + [Test] + public void AccountsForNonInvocationMethodGroupUsageInVariableDeclaration() + { + var input = @" +delegate void FooDelegate (string s); +interface IBase +{ + void Bar(); +} +interface IDerived : IBase { void Foo(string s); - string Property { get; } } -class B : IA +class TestClass { - public virtual void Foo(string s) {} - public string Property { get; } + public void Bar (IDerived derived) + { + derived.Bar(); + FooDelegate d = derived.Foo; + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new ParameterCanBeDemotedIssue(), input, out context); + Assert.AreEqual(0, issues.Count); + } + + [Test] + public void AccountsForNonInvocationMethodGroupUsageInAssignmentExpression() + { + var input = @" +delegate void FooDelegate (string s); +interface IBase +{ + void Bar(); +} +interface IDerived : IBase +{ + void Foo(string s); } class TestClass { - public void F(IA b) + public void Bar (IDerived derived) { - b.Foo(b.Property); + derived.Bar(); + FooDelegate d; + d = derived.Foo; + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new ParameterCanBeDemotedIssue(), input, out context); + Assert.AreEqual(0, issues.Count); + } + + [Test] + public void AccountsForIndexers() + { + var input = @" +class TestClass +{ + void Write(string[] s) + { + object.Equals(s, s); + var element = s[1]; + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new ParameterCanBeDemotedIssue(), input, out context); + Assert.AreEqual(1, issues.Count); + var issue = issues[0]; + // Suggested types: IList and IReadOnlyList + Assert.AreEqual(2, issue.Actions.Count); + + CheckFix(context, issues [0], @" +class TestClass +{ + void Write(System.Collections.Generic.IList s) + { + object.Equals(s, s); + var element = s[1]; } }"); } + + [Test] + public void AccountsForArrays() + { + var input = @" +class TestClass +{ + void Write(string[] s) + { + var i = s.Length; + SetValue (out s[1]); + } + + void SetValue (out string s) + { + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new ParameterCanBeDemotedIssue(), input, out context); + Assert.AreEqual(0, issues.Count); + } } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/ParameterCanBeDemotedIssue/SupportsIndexingCriterionTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/ParameterCanBeDemotedIssue/SupportsIndexingCriterionTests.cs new file mode 100644 index 0000000000..ab2d36dd31 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/ParameterCanBeDemotedIssue/SupportsIndexingCriterionTests.cs @@ -0,0 +1,94 @@ +// +// SupportsIndexingCriterionTests.cs +// +// Author: +// Simon Lindgren +// +// Copyright (c) 2012 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using NUnit.Framework; +using ICSharpCode.NRefactory.CSharp.CodeActions; +using ICSharpCode.NRefactory.TypeSystem; +using System.Collections.Generic; +using ICSharpCode.NRefactory.CSharp.Refactoring; +using ICSharpCode.NRefactory.CSharp.Resolver; + +namespace ICSharpCode.NRefactory.CSharp.CodeIssues +{ + [TestFixture] + public class SupportsIndexingCriterionTests + { + ITypeResolveContext typeResolveContext; + + IType intType; + + ICompilation compilation; + + [SetUp] + public void SetUp() + { + compilation = TestRefactoringContext.Create("").Compilation; + typeResolveContext = compilation.TypeResolveContext; + intType = GetIType(); + } + + IType GetIType() + { + return typeof(T).ToTypeReference().Resolve(typeResolveContext); + } + + void AssertMatches(IType candidateType, IType elementType, bool isWriteAccess, params IType[] indexTypes) + { + var criterion = new SupportsIndexingCriterion(elementType, indexTypes, CSharpConversions.Get(compilation), isWriteAccess); + Assert.IsTrue(criterion.SatisfiedBy(candidateType)); + } + + void AssertDoesNotMatch(IType candidateType, IType elementType, bool isWriteAccess, params IType[] indexTypes) + { + var criterion = new SupportsIndexingCriterion(elementType, indexTypes, CSharpConversions.Get(compilation), isWriteAccess); + Assert.IsFalse(criterion.SatisfiedBy(candidateType)); + } + + [Test] + public void ListWithOneIntegerIndex() + { + var intListType = GetIType>(); + AssertMatches(intListType, intType, false, intType); + AssertMatches(intListType, intType, true, intType); + } + + [Test] + public void ListWithTwoIntegerIndexes() + { + var intListType = GetIType>(); + AssertDoesNotMatch(intListType, intType, false, intType, intType); + AssertDoesNotMatch(intListType, intType, true, intType, intType); + } + + [Test] + public void ObjectCandidate() + { + AssertDoesNotMatch(GetIType(), intType, false, intType); + AssertDoesNotMatch(GetIType(), intType, true, intType); + } + } +} + diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 0cfb1ed995..32e4031ee9 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -301,6 +301,7 @@ +