ErrorProne.NET. Part 4. Implementing Extract Method

Last time we looked at one of the possibilities of ErrorProne.NET notifying about incorrect processing of preconditions in the iterator block and asynchronous methods. The analysis itself is not difficult and not of great interest, but fixing implementation is rather curious. The analyzer defines that the asynchronous method contains an “invalid” argument check and offers to select a method to solve this issue:

Last time we looked at one of the possibilities of ErrorProne.NET notifying about incorrect processing of preconditions in the iterator block and asynchronous methods. The analysis itself is not difficult and not of great interest, but fixing implementation is rather curious.

The analyzer defines that the asynchronous method contains an “invalid” argument check and offers to select a method to solve this issue:

ErrorProne.NET Implementing Extract Method


Each fixer must know the issue he or she has to fix. In order to do this, you need to inherit from the CodeFixProvider class and override the FixableDiagnosticIds attribute:

ErrorProne.NET FixableDiagnosticIds


Now you need to implement RegisterCodeFixesAsync method aimed at registration of the action to be conducted for the fix. This is where the main business logic is focused.

This method acquires CodeFixContext as a parameter using which we can get the diagnostics item. We can do it in a rather generalized way:

ErrorProne.NET CodeFixContext


Where GetFirstNodeWithDiagnostic is the extension method which can be used again:

ErrorProne.NET GetFirstNodeWithDiagnostic


Then we have to realize a special case of Extract Method pattern: take a source method (e.g. FooAsync) and divide it in two — leave the precondition check in the first and transfer the body without the precondition to the new one.

In more detail, the solution should look like this:

1. Find the block with preconditions in the source method
2. Select the method:
  • Clone the source method leaving the source signature (the new method must have the same return value type and the same set of parameters)
  • Turn the method into a closed one
  • Delete all the preconditions from the new method
  • Change the source method
  • Remove the context keyword async from the method declaration (yes, the method still returns Task or Task<T> but the compiler will not twist it into the final automatic state).
3. Leave preconditions check in the body of the method
4. Delegate work to the method created at stage 2: return DoMethodAsync(args).

ErrorProne.NET return DoMethodAsync(args)


In this fragment some extension methods are used such as ParameterList.AsArguments() in order to obtain arguments for the method parameter list or WithoutModifiers, making it possible to delete the modifiers from the method. They are rather simple and can be found in the code at github but they are not very large and should not affect the understanding of the processes in this fragment. I'm also not mentioning the source code of PreconditionBlock class, which is also not very complicated and not very important.

Now we only have to register the action within the context:

ErrorProne.NET Register the Action


The last stage is rather simple but it is crucial to make the update atomic. For instance, the following code will not work:

ErrorProne.NET Code not Working


In this case, the source method will be replaced by the updated one but the selected method will not be “pasted.” This is due to the fact that InsertNoesAfter cannot find an updated method in the updated tree.

Conclusion

The relative simplicity of implementation of method selection is related to the fact that we conduct a simple and rather private operation. Due to immutability of Roslyn features, any WithXXX method clones the source tree node, which is a good starting point for implementation of this refactoring. In addition, in this case we do not have to analyze which variables are located in scope and whether they have to be transferred to the new method. We know that we have to delete the preconditions in the new method and leave them in the old one.

This implementation does not protect from collisions if DoMethodName is already present in the class but collision removal will not be very difficult.

Sergey Teplyakov
Expert in .Net, Ñ++ and Application Architecture

Share the knowledge

Still have questions?
Connect with us
Thank you.
Your request has been received.
Thank you!
The form has been submitted successfully.