4

(No intention to start a religious war on coding style, I just need to solve the problem at hand)

I have just been asked to change all variable declarations across the solution to use explicit typing, i.e. anything like:

Dim Result = 3 + 5

should be converted to:

Dim Result As Integer = 3 + 5

Obviously this can't be done through Find/Replace or even with Regex. The two other possibilities I thought of were:

  1. Resharper: Which I just discovered can do this only for C#, not VB.NET.
  2. Macros: Which have been removed since VS2012 (was a news for me).

Is there an alternate way of automating this tedious job? The solution contains hundreds of thousands of lines of code, so doing it by hand is not an option.

This is for VS2017 Community.

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • You could do it using a RegEx, but not foolproof: a `= d+` would always become an Integer, and a `= "` would become a string (unless suffixed with a `C`, then it'd be a char). For every other assignment, say the return value of a function call, your regex won't know the type. You could maybe do something with Roslyn. – CodeCaster Jan 11 '18 at 08:13
  • @CodeCaster: Just tried. Failing on too many cases to be of much use. – dotNET Jan 11 '18 at 08:18
  • 2
    Yeah so [set `Option Explicit` on your project](https://stackoverflow.com/questions/5076851/can-i-set-option-explicit-and-option-strict-on-a-project-solution-level) and fix the errors one by one. Otherwise you'll need a static code analysis tool that can rewrite source files for this. – CodeCaster Jan 11 '18 at 08:20
  • `Option Strict` is already on for all projects of the solution. That doesn't have much to do with the problem. There are odd usages of object instantiation like `Dim hwnd = New WindowInteropHelper(w).Handle` and other stuff like LINQ calls that makes it so difficult. – dotNET Jan 11 '18 at 08:23
  • 1
    Did you try to change the warning setting _Implicit type, object assumed_ to Error? With this setting you can get the list of errors about those variables declared without the As specification and navigate through them with Ctrl+Shift+F12 – Steve Jan 11 '18 at 08:25
  • 1
    You need also Option Infer = NO – Steve Jan 11 '18 at 08:27
  • @Steve: Thanks. That sounds promising. Let me dig after the lunch break. – dotNET Jan 11 '18 at 08:28
  • either way you should start coding and declaring type for your objects. Expecting the compiler to do the job is completely lazy. Do it right the first time! – Chillzy Jan 12 '18 at 05:14
  • @Chillzy: There is a [general agreement](https://stackoverflow.com/questions/41479/use-of-var-keyword-in-c-sharp) that it is a matter of taste. For some people `Dim X as Integer = 343 + 654` is less readable than `Dim X = 343 + 654`. On the other hand, calls like `Dim Y = SomeMethod()` should always use explicit typing so that reader doesn't have to peek into `SomeMethod` to know the type of `Y`. – dotNET Jan 12 '18 at 05:17
  • There is a general agreement too that started over 30 years ago when the compilers had to know the type. Options Explicit and the other options where not available. You aren't going to tell me otherwise. You only know beginners languages. I know every version of basic, qbasic, vb, delphi, c++, c#, Assembly on PC and VAX, Pascal on PC and MAC, turbo pascal, delphi, python, TCL, powershell, Batch files. You want to avoid problems with your code then only one solution.dim z = 4 + 5 returns a integer....how long? 8, 16, 32, 64 bits? float? double? You are trying to make a point that doesn't exist – Chillzy Jan 12 '18 at 13:20

1 Answers1

1

Finally I was able to solve my problem using multiple techniques. Here's what I did in case this helps someone else down the road:

  1. I talked to the concerned parties and we agreed that we do not need to use Explicit Typing in the following scenarios:

    Dim X1 = 45 + 43 'Type can be inferred by the reader easily
    Dim X2 = DirectCast(obj, DataReader) 'Type explicitly mentioned on RHS        
    Dim X3 = New SomeClass() 'Type explicitly mentioned on RHS
    
  2. So the case which was really less readable and where Explicit Typing was needed was the following:

    Dim X4 = SomeMethod() 'Cannot infer X4's type without peeking or using Go To Definition
    
  3. I then used the following RegEx to locate all such instances:

    Dim \w+ = (?!(New|\d|"|DirectCast))
    
  4. I was left with only a few hundred instances, but it was still a huge task to go through them individually, finding the type of RHS method and then typing it on the LHS. So I searched through VS extensions and found Visual Commander that allows us to write, guess what, macros in C# or VB.NET and use Roslyn API to manipulate code elements in any way we want. So I installed it and after playing with it for some time, I was able to write the following macro which extracts the return type of the RHS method and then injects it to the LHS:

    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Text;
    using System.Linq;
    using Microsoft.CodeAnalysis.VisualBasic;
    using Microsoft.CodeAnalysis.VisualBasic.Syntax;
    
    public class C : VisualCommanderExt.ICommand
    {
      public void Run(EnvDTE80.DTE2 DTE, Microsoft.VisualStudio.Shell.Package package)
      {
        serviceProvider = package as System.IServiceProvider;
        Microsoft.VisualStudio.Text.Editor.IWpfTextView textView = GetTextView();
    
        Microsoft.VisualStudio.Text.SnapshotPoint caretPosition = textView.Caret.Position.BufferPosition;
        Microsoft.CodeAnalysis.Document document = caretPosition.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
        Microsoft.CodeAnalysis.VisualBasic.Syntax.LocalDeclarationStatementSyntax invocationExpressionNode =
            document.GetSyntaxRootAsync().Result.FindToken(caretPosition).Parent.AncestorsAndSelf().
                OfType<Microsoft.CodeAnalysis.VisualBasic.Syntax.LocalDeclarationStatementSyntax>().FirstOrDefault();
    
        if (invocationExpressionNode != null)
        {
          Microsoft.CodeAnalysis.SemanticModel semanticModel = document.GetSemanticModelAsync().Result;
    
          var VD = invocationExpressionNode.ChildNodes().FirstOrDefault(c => c.IsKind(SyntaxKind.VariableDeclarator));
    
          if (VD != null)
          {
            var EV = VD.ChildNodes().FirstOrDefault(c => c.IsKind(SyntaxKind.EqualsValue) || c.IsKind(SyntaxKind.EqualsExpression));
    
            if (EV != null)
            {
              object IE = EV.ChildNodes().FirstOrDefault(c => c.IsKind(SyntaxKind.InvocationExpression));
    
              if (IE != null)
              {
                Microsoft.CodeAnalysis.IMethodSymbol methodSymbol =
                  semanticModel.GetSymbolInfo((InvocationExpressionSyntax)IE).Symbol as Microsoft.CodeAnalysis.IMethodSymbol;
    
                string TypeName = methodSymbol.ReturnType.ToString();
                string ToInsert = " As " + TypeName;
                textView.TextBuffer.Insert(((InvocationExpressionSyntax)IE).SpanStart - 2, ToInsert);
              }
            }
          }
        }
      }
    
      private Microsoft.VisualStudio.Text.Editor.IWpfTextView GetTextView()
      {
        Microsoft.VisualStudio.TextManager.Interop.IVsTextManager textManager =
            (Microsoft.VisualStudio.TextManager.Interop.IVsTextManager)serviceProvider.GetService(
                typeof(Microsoft.VisualStudio.TextManager.Interop.SVsTextManager));
        Microsoft.VisualStudio.TextManager.Interop.IVsTextView textView;
        textManager.GetActiveView(1, null, out textView);
        return GetEditorAdaptersFactoryService().GetWpfTextView(textView);
      }
    
      private Microsoft.VisualStudio.Editor.IVsEditorAdaptersFactoryService GetEditorAdaptersFactoryService()
      {
        Microsoft.VisualStudio.ComponentModelHost.IComponentModel componentModel =
            (Microsoft.VisualStudio.ComponentModelHost.IComponentModel)serviceProvider.GetService(
                typeof(Microsoft.VisualStudio.ComponentModelHost.SComponentModel));
        return componentModel.GetService<Microsoft.VisualStudio.Editor.IVsEditorAdaptersFactoryService>();
      }
    
      private System.IServiceProvider serviceProvider;
    }
    
  5. This script did the heavy-lifting for me. I was then able to assign shortcut keys to GoToFindResult1NextLocation and GoToFindResult1PrevLocation commands and traverse through the Find Results, fixing the ones that I needed to.

  6. This code can be improved to provide even more automation by looping through all files in the solution and fix all these lines automatically, but I thought it would be wise to have more control over the lines that are going to be changed.

In the end I was able to fix all occurrances across the solution in a matter of a few minutes. Further I learned a great deal about the structure of Roslyn API too.

dotNET
  • 33,414
  • 24
  • 162
  • 251