using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using ActiveMesa.R2P.Settings;
using JetBrains.Application;
using JetBrains.Application.DataContext;
using JetBrains.Application.Progress;
using JetBrains.Application.Settings;
using JetBrains.DataFlow;
using JetBrains.DocumentModel;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Daemon;
using JetBrains.ReSharper.Daemon.Stages;
using JetBrains.ReSharper.Daemon.Stages.Dispatcher;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.CodeCleanup;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;
namespace Stuff
{
[ElementProblemAnalyzer(new[] { typeof(IInvocationExpression) },
HighlightingTypes = new[] { typeof(IntPowerHighlighting) })]
public class IntPowerProblemAnalyzer : ElementProblemAnalyzer<IInvocationExpression>
{
public static bool InvocationExpressionIsMathPowCall(IInvocationExpression element, out int power)
{
bool isOnMathPow = false;
var r = element.InvocationExpressionReference.Resolve();
var m = r.DeclaredElement as IMethod;
if (m != null)
{
var parent = m.GetContainingType();
if (parent != null)
{
isOnMathPow = parent.GetClrName().FullName.Equals("System.Math")
&& m.ShortName.Equals("Pow");
}
}
bool firstArgIsIdentifier = false;
bool secondArgIsInteger = false;
power = -1;
if (element.Arguments.Count == 2)
{
firstArgIsIdentifier = element.Arguments[0].Value is IReferenceExpression;
var secondArg = element.Arguments[1].Value as ICSharpLiteralExpression;
if (secondArg != null && (secondArg.IsConstantValue()))
{
double value = -1.0;
var cv = secondArg.ConstantValue;
if (cv.IsDouble())
value = (double)cv.Value;
else if (cv.IsInteger())
value = (int)cv.Value;
power = (int)value;
secondArgIsInteger = (value > 0.0) && value == Math.Floor(value);
}
}
return isOnMathPow && firstArgIsIdentifier && secondArgIsInteger;
}
protected override void Run(IInvocationExpression element,
ElementProblemAnalyzerData data, IHighlightingConsumer consumer)
{
int power;
if (InvocationExpressionIsMathPowCall(element, out power))
consumer.AddHighlighting(new IntPowerHighlighting(element, power),
element.GetDocumentRange(), element.GetContainingFile());
}
}
[StaticSeverityHighlighting(Severity.WARNING, CSharpLanguage.Name)]
public class IntPowerHighlighting : IHighlighting
{
public IInvocationExpression Expression { get; private set; }
public int Power { get; private set; }
public IntPowerHighlighting(IInvocationExpression expression, int power)
{
Expression = expression;
Power = power;
}
public bool IsValid()
{
return Expression != null && Expression.IsValid();
}
public string ToolTip { get { return "Inefficient use of integer-based power"; } }
public string ErrorStripeToolTip { get { return ToolTip; } }
public int NavigationOffsetPatch { get { return 0; } }
}
[QuickFix]
public class IntPowerInliningFix : BulbItemImpl, IQuickFix
{
private readonly IntPowerHighlighting highlighting;
private Pair<bool, string> customNameSettings;
public IntPowerInliningFix(IntPowerHighlighting highlighting)
{
this.highlighting = highlighting;
customNameSettings = GetCustomFunctionSettings(highlighting.Expression.ToDataContext());
}
public static void PerformChange(IInvocationExpression expr, Pair<bool,string> customNameSettings, int power)
{
var arg = expr.Arguments[0];
var factory = CSharpElementFactory.GetInstance(expr.GetPsiModule());
ICSharpExpression replacement;
if (customNameSettings.First && power > 3)
{
var template = "$0($1, " + power + ")";
replacement = factory.CreateExpression(template,
customNameSettings.Second, expr.Arguments[0]);
}
else
{
replacement = factory.CreateExpression(
Enumerable.Range(0, power).Select(i => "$0").Join("*"), arg.Value);
}
ModificationUtil.ReplaceChild(expr, replacement);
}
protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
{
PerformChange(highlighting.Expression, customNameSettings, highlighting.Power);
return null;
}
public static Pair<bool, string> GetCustomFunctionSettings(Func<Lifetime, DataContexts, IDataContext> ctx)
{
var ss = Shell.Instance.GetComponent<ISettingsStore>();
var bs = ss.BindToContextTransient(ContextRange.Smart(ctx));
var s = bs.GetKey<GeneralSettings>(SettingsOptimization.DoMeSlowly);
return new Pair<bool, string>(s.UseCustomPowerFunction, s.CustomPowerFunctionName);
}
public override string Text
{
get
{
return customNameSettings.First
? "Use '{0}' instead".ƒ(customNameSettings.Second)
: "Inline integer power";
}
}
public bool IsAvailable(IUserDataHolder cache)
{
return highlighting.Power >= 2;
}
}
[CodeCleanupModule]
public class IntPowerInliningCleanup : ICodeCleanupModule
{
private static readonly Descriptor descriptor = new Descriptor();
private IShellLocks shellLocks;
public IntPowerInliningCleanup(IShellLocks shellLocks)
{
this.shellLocks = shellLocks;
}
public void SetDefaultSetting(CodeCleanupProfile profile, CodeCleanup.DefaultProfileType profileType)
{
switch (profileType)
{
case CodeCleanup.DefaultProfileType.FULL:
profile.SetSetting(descriptor, true);
break;
default:
profile.SetSetting(descriptor, false);
break;
}
}
public bool IsAvailable(IPsiSourceFile sourceFile)
{
return sourceFile.GetPsiFile<CSharpLanguage>() != null;
}
public void Process(IPsiSourceFile sourceFile, IRangeMarker rangeMarkerMarker,
CodeCleanupProfile profile, IProgressIndicator progressIndicator)
{
var file = sourceFile.GetPsiFile<CSharpLanguage>();
if (file == null)
return;
if (!profile.GetSetting(descriptor))
return;
var settings = IntPowerInliningFix.GetCustomFunctionSettings(sourceFile.ToDataContext());
file.GetPsiServices().PsiManager.DoTransaction(() =>
{
using (shellLocks.UsingWriteLock())
{
var itemsToChange = new List<Pair<IInvocationExpression,int>>();
file.ProcessChildren<IInvocationExpression>(e =>
{
int power;
if (IntPowerProblemAnalyzer.InvocationExpressionIsMathPowCall(e, out power))
itemsToChange.Add(new Pair<IInvocationExpression, int>(e, power));
});
foreach (var e in itemsToChange)
IntPowerInliningFix.PerformChange(e.First, settings, e.Second);
}
}, "Code cleanup");
}
public PsiLanguageType LanguageType
{
get { return CSharpLanguage.Instance; }
}
public ICollection<CodeCleanupOptionDescriptor> Descriptors
{
get { return new[] {descriptor}; }
}
public bool IsAvailableOnSelection
{
get { return false; }
}
[DefaultValue(false)]
[DisplayName("Replace Math.Pow() integer calls")]
[Category(CSharpCategory)]
private class Descriptor : CodeCleanupBoolOptionDescriptor
{
public Descriptor() : base("ReplaceMathPowIntegerCalls") {}
}
}
}