Tuesday, November 24, 2015

Find code patterns using C# and Roslyn: Introducing IntelliFind

Since the beginning of time, code editors have been equipped with ‘Find’ and / or ‘Find All’ features. This allows the user to search for any or all occurrences of literal text in a code base. Most editors also allow you to use regular expressions for more ‘advanced’ searches.

But what if you need to search for all variables with the name ‘goldCustomer’, because someone decided these need to be renamed to ‘specialCustomer’. Or search for methods with a set of tree parameters named “name”, “dateOfBirth” and "address”, because these need to be refactored to use a new Person class. Or your codebase is filled with another repeating pattern that seemed to be a good idea to someone at one point, but now hinders development big time. These kind of searches are not easily done with simple text search or regular expressions. You need a search tool that understands the syntax of your code in order to search for these kinds of patterns.

Fortunately we now have the Roslyn compiler engine that understands the structure of C# code and exposes this structure in the form of syntax trees. You can use these syntax trees to analyze code by creating custom analyzers like I did here. For ad-hoc searches in day to day work however writing a custom analyzer for one time use is just way to much work. It involves creating a new solution in a new visual studio instance, writing the code for the analyzer, debugging it in the experimental Visual Studio Instance and then install it into yet another instance in order to search your target code base. It would be nice if we could simplify this.

Therefore I decided to create a Visual Studio Extension that allows you to search for code using the Roslyn API, without leaving the Visual Studio instance where the code you are searching in is loaded. It looks like this:



Inside the IntelliFind tool window you can type C# expressions like the following:

// All variables named ‘goldCustomer’
AllNodes.OfType<VariableDeclaratorSyntax>().Where(v=>v.Identifier.Text=="goldCustomer")


or

// Find all methods with parameters "name" ,"dateOfBirth", "address" 
AllNodes.OfType<ParameterListSyntax>()
 .Where(pl => pl.Parameters.Select(param => param.Identifier.Text)
  .SequenceEqual(new[] {"name", "dateOfBirth", "address"}))


Searching C# code using C# seems like a very natural fit. C# developers typically know C# (I would hope) while only a few of us are comfortable with more advanced regular Expressions. Because IntelliFind uses the Roslyn syntax trees you can take advantage of Roslyns knowledge of the code structure. You can filter on the type of a SyntaxNode, names of identifiers, the structure of the syntax tree or you could use the semantic model to inspect the types of an expression and so on. The results of your query are displayed in a list view indicating the file path, the location in the file, the type of object that was found and the first part of the code that was found. Like in a normal search result window you can double click an item to jump to the corresponding source in the code editor.

Inside the IntelliFind window you can write any valid C# code, this includes writing methods or classes, whatever you’d like. This code will be executed by the new CSharp Scripting API (currently in pre release). The script gets some global objects in scope that give you access to the Roslyn object model of the currently loaded solution.
  • Workspace
  • The Workspace that is loaded in Visual Studio. This is Roslyns root object that gives you access to the CurrentSolution, Projects, Documents etc.

  • AllNodes
  • An IEnumerable<syntaxnode> that contains all the SyntaxNodes in the entire solution. This is most useful for solution wide searches like the examples above. This is actually short for
    Workspace.CurrentSolution.Projects
      .SelectMany(p => p.Documents)
      .SelectMany(d => d.GetSyntaxRootAsync(CancellationToken)
                           .Result.DescendantNodes());
    

  • SelectedToken
  • The SyntaxToken that is selected in the active code window. This enables searches relative to the selected text in the editor, like finding all members of a given class, all local variables within a method etc.

  • ActiveDocument
  • The Document object of the active code window. This enables searches scoped to the current document.

  • CancellationToken
  • A CancelationToken that will be fired when the user clicks Cancel during a search, you can propagate this to potentially long running operations that support Cancellation like GetSyntaxRootAsync().

I have made this extension available for download at the Visual Studio Gallery. You can also stay in Visual Studio and go to Tools -> 'Extensions and Updates' and search the Online Gallery for ‘IntelliFind’. Once installed the IntelliFind window can be opened from View -> Other Windows -> IntelliFind. I would really love to hear what you think of this extension. How do you think this will help you and what do you think can be improved?

I have also put the source code on GitHub so you can see how it works and of course you are welcome to contribute as well. Obviously there is still a lot of room for improvement. Most importantly it would be nice to have Intellisense inside the search Window. C# has been designed with Intellisense in mind, typing C# in a normal text box using a possibly non-familiar API is not nearly as productive as a C# developer is used to. Furthermore, any Find-feature should be paired with a companion Replace-feature.

No comments:

Post a Comment