I am trying to get a general overview on how to implement plugins for ReSharper. I was able to develop a very simple plugin, deploy it, and use it to perform action that I desire.
However, I wanted to change my plugin to support bulk action mode. Therefore I first followed official ReSharper guidelines which are outdated and only valid for v8.0 at the moment. Then I discovered this github issue and it became clear that bulk actions are not yet properly documented. However, I was able to put "something" together based on google groups post that is referenced in the github issue.
I wanted to implement something really simple and stupid, just a proof of concept, therefore I decompiled some of the ReSharper assemblies and copied the implementation of ToggleVarFix
into my solution. I modified it according to google groups post and made a functional change - original version replaces explicit type by var
, my custom version replaces it by abstract
. Which of course doesn't make sense, I just wanted to see the bulk action in action.
Surprisingly for me, my plugin still works only in single fix mode, i.e. I am able to change explicit type to abstract
keyword for a single declaration. However, once I try to invoke my action in bulk mode (for file or project), the explicit type gets changed to var
and I really have no idea why is this happening and why it isn't abstract
instead. The following image explains better what I don't get about how my plugin currently works:
Could somebody review the implementation of my plugin and perhaps suggest what I did wrong? Below is my code, the only "functional" change comparing against original ToggleVarFix
should be commented out declaration.SetVar();
, replaced by a slightly modified version where abstract
is used as a replacement for explicit types as opposed to var
from original version.
using System;
using JetBrains.Annotations;
using JetBrains.Application.Progress;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Daemon.CSharp.CodeCleanup.CodeStyles;
using JetBrains.ReSharper.Daemon.CSharp.Errors;
using JetBrains.ReSharper.Feature.Services.Bulk;
using JetBrains.ReSharper.Feature.Services.Bulk.Actions;
using JetBrains.ReSharper.Feature.Services.CodeCleanup;
using JetBrains.ReSharper.Feature.Services.QuickFixes;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.CodeStyle;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Resources.Shell;
using JetBrains.TextControl;
using JetBrains.Util;
namespace RSharpExtensionSample
{
[QuickFix]
public class CustomToggleVarFix : QuickFixBase, ICodeCleanupAction
{
private static readonly Key InstanceKey = new Key("CustomToggleVarFix");
private readonly ITreeNode m_myDeclaration;
private readonly CodeCleanupProfile m_myCodeCleanupProfile;
private readonly IProjectFile m_myProjectFile;
public override string Text
{
get { return IsConvertingToVar() ? "[Custom] Use 'var'" : "[Custom] Use explicit type"; }
}
public string BulkText
{
get { return Text + " everywhere"; }
}
public FileCollectorInfo FileCollectorInfo
{
get { return new FileCollectorInfo(m_myProjectFile, CSharpLanguage.Instance); }
}
public bool Single
{
get { return false; }
}
public CustomToggleVarFix([NotNull] UseVarOrTypeForBuiltInTypesWarning warning)
: this(warning.Declaration)
{
}
public CustomToggleVarFix([NotNull] UseVarOrTypeElsewhereWarning warning)
: this(warning.Declaration)
{
}
public CustomToggleVarFix([NotNull] UseVarOrTypeForSimpleTypesWarning warning)
: this(warning.Declaration)
{
}
private CustomToggleVarFix([NotNull] ITreeNode node)
{
m_myDeclaration = node;
m_myProjectFile = m_myDeclaration.GetSourceFile().ToProjectFile();
var value = IsConvertingToVar() ? VariableStyle.UseVar : VariableStyle.UseExplicitType;
var component = Shell.Instance.GetComponent<CodeCleanupSettingsComponent>();
m_myCodeCleanupProfile = component.CreateEmptyProfile("Test");
m_myCodeCleanupProfile.SetSetting(ReplaceByVarCodeCleanupModule.DESCRIPTOR, true);
m_myCodeCleanupProfile.SetSetting(ReplaceByVarCodeCleanupModule.OPTIONS, new ReplaceByVarCodeCleanupModule.Options {
ForBuiltInTypes = value,
ForSimpleTypes = value,
ForOtherTypes = value
});
}
protected override Action<ITextControl> ExecutePsiTransaction([NotNull] ISolution solution, [NotNull] IProgressIndicator progress)
{
TryExecuteFor(m_myDeclaration as IMultipleLocalVariableDeclaration);
TryExecuteFor(m_myDeclaration as IForeachVariableDeclaration);
return null;
}
private void TryExecuteFor(IForeachVariableDeclaration declaration)
{
if (declaration == null)
{
return;
}
if (declaration.VarKeyword == null)
{
//declaration.SetVar();
ChangeExplicitTypeToAbstract(declaration);
return;
}
declaration.SetType(declaration.DeclaredElement.Type);
}
private void TryExecuteFor(IMultipleLocalVariableDeclaration declaration)
{
if (declaration == null)
{
return;
}
if (declaration.VarKeyword == null)
{
//declaration.SetVar();
ChangeExplicitTypeToAbstract(declaration);
return;
}
var localVariableDeclaration = declaration.Declarators[0] as ILocalVariableDeclaration;
if (localVariableDeclaration == null)
{
return;
}
var declaredElement = localVariableDeclaration.DeclaredElement;
localVariableDeclaration.SetType(declaredElement.Type);
}
private static void ChangeExplicitTypeToAbstract(IMultipleLocalVariableDeclaration declaration)
{
using (WriteLockCookie.Create(declaration.IsPhysical()))
{
ModificationUtil.ReplaceChild(
declaration.TypeDesignator,
JetBrains.ReSharper.Psi.CSharp.Parsing.CSharpTokenType.ABSTRACT_KEYWORD.CreateLeafElement());
}
}
private static void ChangeExplicitTypeToAbstract(IForeachVariableDeclaration declaration)
{
if (declaration.TypeUsage == null) return;
using (WriteLockCookie.Create(declaration.IsPhysical()))
{
ModificationUtil.ReplaceChild(
declaration.TypeUsage,
JetBrains.ReSharper.Psi.CSharp.Parsing.CSharpTokenType.ABSTRACT_KEYWORD.CreateLeafElement());
}
}
public override bool IsAvailable(IUserDataHolder cache)
{
if (!IsAvailableEx()) return false;
cache.PutData(InstanceKey, this);
return true;
}
private bool IsAvailableEx()
{
return CSharpExtensionMethods.IsCSharp3Supported(m_myDeclaration)
&& (IsAvailableFor(m_myDeclaration as IMultipleLocalVariableDeclaration)
|| IsAvailableFor(m_myDeclaration as IForeachVariableDeclaration));
}
private bool IsAvailableFor(IForeachVariableDeclaration declaration)
{
return declaration != null;
}
private bool IsAvailableFor(IMultipleLocalVariableDeclaration declaration)
{
return declaration != null && declaration.Declarators.Count == 1;
}
private bool IsConvertingToVar()
{
var multipleLocalVariableDeclaration = m_myDeclaration as IMultipleLocalVariableDeclaration;
if (multipleLocalVariableDeclaration != null)
{
return multipleLocalVariableDeclaration.VarKeyword == null;
}
var foreachVariableDeclaration = m_myDeclaration as IForeachVariableDeclaration;
if (foreachVariableDeclaration != null)
{
return foreachVariableDeclaration.VarKeyword == null;
}
throw new InvalidOperationException();
}
public CodeCleanupProfile GetCodeCleanupProfile()
{
return m_myCodeCleanupProfile;
}
}
}