1

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:

enter image description here

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;
        }
    }
}
Michal Hosala
  • 5,570
  • 1
  • 22
  • 49

0 Answers0