From 4a3192df58b57a874380e73709a4347f7a63360d Mon Sep 17 00:00:00 2001 From: Simon Lindgren Date: Mon, 25 Jun 2012 12:15:48 +0200 Subject: [PATCH] [CodeIssues] Add CallToStaticMemberViaDerivedTypeIssue. --- .../ICSharpCode.NRefactory.CSharp.csproj | 1 + .../CallToStaticMemberViaDerivedTypeIssue.cs | 85 ++++++++ .../CallToStaticMemberViaDerivedTypeTests.cs | 205 ++++++++++++++++++ .../ICSharpCode.NRefactory.Tests.csproj | 1 + 4 files changed, 292 insertions(+) create mode 100644 ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/CallToStaticMemberViaDerivedTypeIssue.cs create mode 100644 ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/CallToStaticMemberViaDerivedTypeTests.cs diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index 13816f1db0..58f84ad963 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -385,6 +385,7 @@ + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/CallToStaticMemberViaDerivedTypeIssue.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/CallToStaticMemberViaDerivedTypeIssue.cs new file mode 100644 index 0000000000..a436b3a59c --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/CallToStaticMemberViaDerivedTypeIssue.cs @@ -0,0 +1,85 @@ +// +// CallToStaticMemberViaDerivedTypeIssue.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.Collections.Generic; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + [IssueDescription("Call to static member via a derived class", + Description = "Suggests using the class declaring a static function when calling it.", + Category = IssueCategories.CodeQualityIssues, + Severity = Severity.Suggestion)] + public class CallToStaticMemberViaDerivedTypeIssue : ICodeIssueProvider + { + #region ICodeIssueProvider implementation + public IEnumerable GetIssues(BaseRefactoringContext context) + { + return new GatherVisitor(context).GetIssues(); + } + + class GatherVisitor : GatherVisitorBase + { + readonly BaseRefactoringContext context; + + public GatherVisitor(BaseRefactoringContext context) : base (context) + { + this.context = context; + } + + public override void VisitInvocationExpression(InvocationExpression invocationExpression) + { + base.VisitInvocationExpression(invocationExpression); + var invocationResolveResult = context.Resolve(invocationExpression) as InvocationResolveResult; + if (invocationResolveResult == null) + return; + if (!invocationResolveResult.Member.IsStatic) + return; + var targetResolveResult = invocationResolveResult.TargetResult as TypeResolveResult; + if (targetResolveResult == null) + return; + if (targetResolveResult.Type.Equals(invocationResolveResult.Member.DeclaringType)) + return; + AddIssue(invocationExpression.Target, context.TranslateString("Static method invoked via derived type"), + GetActions(context, invocationExpression, invocationResolveResult.Member)); + } + + IEnumerable GetActions(BaseRefactoringContext context, InvocationExpression invocationExpression, + IMember member) + { + var csResolver = context.Resolver.GetResolverStateBefore(invocationExpression); + var builder = new TypeSystemAstBuilder(csResolver); + var newType = builder.ConvertType(member.DeclaringType); + string description = string.Format("{0} '{1}'", context.TranslateString("Use base class"), newType.GetText()); + yield return new CodeAction(description, script => { + script.Replace(((MemberReferenceExpression)invocationExpression.Target).Target, newType); + }); + } + } + #endregion + } +} + diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/CallToStaticMemberViaDerivedTypeTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/CallToStaticMemberViaDerivedTypeTests.cs new file mode 100644 index 0000000000..d9bd8b1c84 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/CallToStaticMemberViaDerivedTypeTests.cs @@ -0,0 +1,205 @@ +// +// CallToStaticMemberViaDerivedType.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.CSharp.Refactoring; + +namespace ICSharpCode.NRefactory.CSharp.CodeIssues +{ + [TestFixture] + public class CallToStaticMemberViaDerivedTypeTests : InspectionActionTestBase + { + [Test] + public void BasicCase() + { + var input = @" +class A +{ + public static void F() { } +} +class B : A { } +class C +{ + void Main() + { + B.F(); + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new CallToStaticMemberViaDerivedTypeIssue(), input, out context); + Assert.AreEqual(1, issues.Count); + Assert.AreEqual(11, issues [0].Start.Line); + + CheckFix(context, issues [0], @" +class A +{ + public static void F() { } +} +class B : A { } +class C +{ + void Main() + { + A.F(); + } +}"); + } + + [Test] + public void NestedClass() + { + var input = @" +class A +{ + public class B + { + public static void F() { } + } + public class C : B { } +} +class D +{ + void Main() + { + A.C.F(); + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new CallToStaticMemberViaDerivedTypeIssue(), input, out context); + Assert.AreEqual(1, issues.Count); + Assert.AreEqual(14, issues [0].Start.Line); + + CheckFix(context, issues [0], @" +class A +{ + public class B + { + public static void F() { } + } + public class C : B { } +} +class D +{ + void Main() + { + A.B.F(); + } +}"); + } + + [Test] + public void ExpandsTypeWithNamespaceIfNeccessary() + { + var input = @" +namespace First +{ + class A + { + public static void F() { } + } +} +namespace Second +{ + public class B : First.A { } + class C + { + void Main() + { + B.F(); + } + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new CallToStaticMemberViaDerivedTypeIssue(), input, out context); + Assert.AreEqual(1, issues.Count); + Assert.AreEqual(16, issues [0].Start.Line); + + CheckFix(context, issues [0], @" +namespace First +{ + class A + { + public static void F() { } + } +} +namespace Second +{ + public class B : First.A { } + class C + { + void Main() + { + First.A.F(); + } + } +}"); + } + + [Test] + public void IgnoresCorrectCalls() + { + var input = @" +class A +{ + public static void F() { } +} +class B +{ + void Main() + { + A.F(); + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new CallToStaticMemberViaDerivedTypeIssue(), input, out context); + Assert.AreEqual(0, issues.Count); + } + + [Test] + public void IgnoresNonStaticCalls() + { + var input = @" +class A +{ + public void F() { } +} +class B : A { } +class C +{ + void Main() + { + B b = new B(); + b.F(); + } +}"; + TestRefactoringContext context; + var issues = GetIssues(new CallToStaticMemberViaDerivedTypeIssue(), input, out context); + Assert.AreEqual(0, issues.Count); + } + } +} + diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 80c09d1034..6e8acaeb34 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -281,6 +281,7 @@ +