ModificationUtil.ReplaceChild throws NullReferenceException

I have a plugin that I am upgrading to v5.0. The plugin converts built in value type to and from CLR and alias type representations. As part of this update, I have also added a context action to the refactoring.

When I run the change via code cleanup, everything works fine. When run as a context action, ModificationUtil.ReplaceChild throws a NullReferenceException (see http://neovolvex.codeplex.com/SourceControl/changeset/view/46080#914800 for the source code of the context action).

   at JetBrains.ReSharper.Psi.Impl.PsiManagerImpl.PsiTransactionManager.AddAction(IPsiTransactionAction transactionAction)
   at JetBrains.ReSharper.Psi.Impl.PsiManagerImpl.PsiManagerImpl.AddTransactionAction(IPsiTransactionAction action)
   at JetBrains.ReSharper.Psi.Impl.PsiManagerImpl.PsiManagerImpl.AddTransactionAction(IPsiTransactionAction action, IElement elementContainingChanges, PsiChangedElementType changedElementType)
   at JetBrains.ReSharper.Psi.ExtensionsAPI.LowLevelModificationUtil.ReplaceChildRange(ITreeNode first, ITreeNode last, ITreeNode[] nodes)
   at JetBrains.ReSharper.Psi.ExtensionsAPI.Tree.ModificationUtil.ReplaceChild[T](ITreeNode oldChild, T newChild)
   at Neovolve.Extensibility.ReSharper.NodeTypeConverterBase.ReplaceNode(ICSharpTreeNode newNode)
   at Neovolve.Extensibility.ReSharper.NodeTypeConverterBase.ToClrType()
   at Neovolve.Extensibility.ReSharper.NodeProcessor.UpdateTypeUsage(TypeConversionResult conversionResult, Int32 commentIndex)
   at Neovolve.Extensibility.ReSharper.ConvertToClrTypeBulbItem.Execute(ISolution solution, ITextControl textControl)
   at JetBrains.ReSharper.Intentions.Bulbs.BulbMenuItem.Clicked(BulbContext context)
   at JetBrains.ReSharper.Intentions.Bulbs.BulbIndicator.OnMenuItemClicked(Object key)
   at JetBrains.ReSharper.Intentions.Bulbs.BulbIndicator.<>c__DisplayClass14.<Run>b__12(Object o)
   at JetBrains.DataFlow.Signal`1.NotifySinks(TValue payload)

The same code line is being used in both cases. The only thing that seems different to me is how the node is obtained. In the code cleanup module, the entire tree of the file is traversed (taking into account the selected range). In the context action, the following is used to obtain the node.


            ITokenNode tokenNode = _provider.GetSelectedElement<ITokenNode>(false, true);
            
            if (tokenNode == null)
            {
                return false;
            }
            
            ITreeElementPointer<ITokenNode> elementPointer = _provider.PsiManager.CreateTreeElementPointer(tokenNode);


I have tried using both the node and a pointer to the node with the same exception being thrown.

My temporary code to try to debug this is using the following to create and replace the node in question.


            using (WriteLockCookie.Create())
            {
                StringBuffer buffer = new StringBuffer("Int32");
                ITreeNode myNewNode = CSharpTokenType.INTEGER_LITERAL.Create(buffer, TreeOffset.Zero, new TreeOffset(buffer.Length));
                ModificationUtil.ReplaceChild(Node, myNewNode);
            }

Any ideas where I'm going wrong?

Cheers,

Rory

5 comments
Comment actions Permalink

We have 2 build of ReSharper - production and development. The differece is the production build doesn't have any assertion and consistemcy checks. To develop plugins, it is recommended to use development builds to get error message earlier.

For ReSharper 5.0, development build could be found here: http://download.jetbrains.com/resharper/ReSharperSetup.5.0.1659.39.msi
For ReSharper 5.1, current nightly builds are develpment ones.

As of your problem, looks like you've not started PsiTransaction before you started modifying tree. In CodeCleanup, modules are executed under PSI transaction, for context actions you should start them manually.

PsiManager.GetInstance(.....).DoTransaction(() => { ...here code to be executed under PSI transaction.....  });

0
Comment actions Permalink

We have 2 build of ReSharper - production and development. The differece is the production build doesn't have any assertion and consistemcy checks. To develop plugins, it is recommended to use development builds to get error message earlier.

For ReSharper 5.0, development build could be found here: http://download.jetbrains.com/resharper/ReSharperSetup.5.0.1659.39.msi
For ReSharper 5.1, current nightly builds are develpment ones.

As of your problem, looks like you've not started PsiTransaction before you started modifying tree. In CodeCleanup, modules are executed under PSI transaction, for context actions you should start them manually.

PsiManager.GetInstance(.....).DoTransaction(() => { ...here code to be executed under PSI transaction.....  });

0
Comment actions Permalink

Hi Eugene,

Thanks, that was the problem and now the node replacement works.

I have noticed something in my testing. Sometimes there are two "Text Buffer Change" entries in the undo list when I replace nodes with a different text lengths.

For example, my bulb item is changing an IDocCommentNode

/// The <see cref="float"/> value with another <see cref="char"/> value.

to

/// The <see cref="Single"/> value with another <see cref="char"/> value.

The first item in the undo list changes the comment to

/// The <see cref="fle"/> value with another <see cref="char"/> value.

with the second undo going to back to the original comment.

My code uses the following method to create the new comment node.


private ITreeNode CreateNewCommentNode(String newTypeName)

        {


String nodeText = Node.GetText();


String newNodeText = nodeText.Substring(0, CommentCaretIndex) + newTypeName + nodeText.Substring(CommentCaretIndex + _typeName.Length);


StringBuffer buffer = new StringBuffer(newNodeText);


TreeOffset endOffset = new TreeOffset(newNodeText.Length);


return CSharpTokenType.END_OF_LINE_COMMENT.Create(buffer, TreeOffset.Zero, endOffset);

        }


It then uses this code to replace the existing IDocCommentNode with the new one.


        private void ReplaceNode(ITreeNode newNode)


        {


// replace AST fragment in actual file




using (WriteLockCookie.Create())


            {


ModificationUtil.ReplaceChild(Node, newNode);


            }

        }



The same thing happens when creating nodes that refer type CLR type names when the original was an alias type reference. In these cases, the code that creates the node is:


        protected override ITreeNode CreateClrTypeNode()

        {


StringBuffer buffer = new StringBuffer(TypeMapping.ClrTypeName);


return CSharpTokenType.IDENTIFIER.Create(buffer, TreeOffset.Zero, new TreeOffset(buffer.Length));

        }


This then uses the same node replacement method.

Any idea why there are two changes in the undo list?

Is there a way to customize the text in the undo list to be the text from my bulb item?

0
Comment actions Permalink

I've changed the IContextAction to inherit from BulbItemImpl which seems to take care of this issue.

0
Comment actions Permalink

We do use some kind of diff to promote AST changes into document changes. This is to minimize document changes. Looks like you've catch that artefact of this.

To group all your actions into single Undo item, please embrace the whole code into CommandCookie.


{code}
  using (CommandCookie.Create("Text to be visible in the undo command"))
  {
     .... All the stuff here, including PSI transactions, all document modifications, etc.
  }
{code}

0

Please sign in to leave a comment.