Preview Build is Here!

We couldn’t hold it much longer. (was about time!)

So we issued a preview build. Keep in mind that this is a preview build – we know it’s not completed, there are little glitches and issues left and right, things to polish a bit more, it may hang or crash your host application.

And it’s missing features: the Code Explorer (and its “virtual folders”) aren’t part of this release – that’s really not ready.

The Smart Indenter port is there though, and although the preview box doesn’t show it in the settings dialog, it works pretty well.

IDE-Integrated Git Source Control is there too, and works nicely as well.

A brand new shiny WPF UI for all docked toolwindows, a new settings dialog, “why?” rationale for every inspection result, “fix ’em all” across the project/module, or disable inspections with a single click.

Our parser/resolver is much more powerful now – all known parser issues (as of v1.4.3) are now fixed, and new ones you never encountered too.

Unit testing works in AutoCAD and CorelDRAW now, and although the settings have no effect, the API is there and you can use a new PermissiveAssertClass that evaluates Equals in a less strict type-safe way, to work more like VB6 would do it.

There’s a lot to say and a lot will be said I’m sure.

Poke around, play with it – by all means, break it – and if you do break it, make sure you let us know how, so we can fix it by the time we ship the “real” 2.0 build.

Apparently there’s still a few inspection false positives (although it should be much better than in 1.x), so make sure you double-check before you fix all occurrences in project.

vbe2.PNG

To be continued…

You’ve waited long enough.

The wait is over!

I have to say that this release has been… exhausting. Correctly resolving identifier references has proven to be much, much more complicated than I had originally anticipated. VBA has multiple ways of making this hard: with blocks are but one example; user-defined-type fields are but another.

But it’s done. And as far as I could tell, it works.

Why did you tag it as “pre-release” then?

Because resolving identifier references in VBA is hard, and what I released is not without issues; it’s not perfect and still needs some work, but I believe most normal use cases are covered.

For example, this code will blow up with a wonderful StackOverflowException:

Class1

Public Function Foo() As Class2
    Set Foo = New Class2
End Function

Class2

Public Sub Foo()
End Sub

ThisWorkbook

Public Sub DoSomething()
    Dim Foo As New Class1
    With Foo
            .Foo
    End With
End Sub

It compiles, VBA resolves it. And it’s fiendish, and nobody in their right minds would do anything near as ambiguous as that. But it’s legal, and it blows up.

That’s why I tagged it as a “pre-release”: because there are a number of hair-pulling edge cases that just didn’t want to cooperate.

See, finding all references to “foobar” works very well here:

Public Sub DoSomething()
    Dim foobar As New Class1
    With foobar
        With .Foo
            .Bar
        End With
    End With
End Sub

…and finding all references to “Foo” in the below code will not blow up, but the “.Foo” in the 2nd with block resolves as a reference to the local variable “Foo”:

Public Sub DoSomething()
    Dim Foo As New Class1
    With Foo
        With .Foo
            .Bar
        End With
    End With
End Sub

And of course, there are a number of other issues still.

Here’s a non-exhaustive list of relatively minor known issues we’re postponing to 1.31 or other future release – please don’t hesitate to submit a new issue if you find anything that doesn’t work as you’d expect.

There can be only one

Rubberduck doesn’t [yet] handle cross-project references; while all opened VBA projects are parsed and navigatable, they’re all “silos”, as project references aren’t taken into account; this means if project A is referencing project B, and project A contains the only usage of a procedure defined in project B, then that procedure will fire up a code inspection saying the procedure is never used.

It also means “find all references” and “rename” will miss it.

self-referencing Parameters

“Find all references” has been seen listing the declaration of a parameter in its list of references. Not a biggie, but not intended either. There’s no explanation for that one, yet – in fact it’s possible you never even encounter this issue.

Selection Glitches From Code Inspection Results

We know what causes it: the length of the selection is that of the last word/token in the parser context associated with the inspection result. That’s like 80% fixed! Now the other 80% is a little bit tricky…

Performance

Code inspections were meant to get faster. They got more accurate instead. This needs a tune-up. You can speed up inspections by turning off the most expensive ones… although these are often the most useful ones, too.

Function return vAlue not assigned

There has been instances of [hard-to-repro] object-typed property getters that fire up an inspection result, when they shouldn’t. Interestingly this behavior hasn’t been reproduced in smaller projects. This inspection is important because a function (or property getter) that’s not assigned, will not return anything – and that’s more than likely a bug waiting to be discovered.

Parameter can be passed by value

This inspection is meant to indicate that a ByRef parameter is not assigned a value, and could safely be passed ByVal. However if such a parameter is passed to another procedure as a ByRef parameter, Rubberduck should assume that the parameter is assigned. That bit is not yet implemented, so that inspection result should be taken with a grain of salt (like all code inspection results in any static code analysis tool anyway).

This inspection will not fire up a result if the parameter isn’t a primitive type, for performance reason (VBA performance); if performance is critical for your VBA code, passing parameters by reference may yield better results.

While we’re waiting…

I cannot wait to release the next version. Seriously. You have no idea how much it itches.

It’s coming, no worries; we only have a few tiny little adjustments to make and we’re ready.

In the mean time, v1.22 had over 160 downloads (probably 170 by the time I publish this post), which is more than anything I would have expected. After all, in the past 30 days I’ve fixed so many bugs I’ve come to see 1.22 as an under-featured buggy tool – code inspections are definitely promising, but the parser has (had!) too many issues for a lot of them to be reliable. Fortunately version 1.3 changes that.

We’ve had awesome feedback already, some on GitHub, some via our contact us form on rubberduck-vba.com, some on Twitter… but hardly any here.

So I’m going to include some new features of v1.3 here (just to skew the results a bit – and I’m also biting my tongue on very cool things coming up for v2.0), and ask you…

Comments are welcome!

Version 1.22 – Hotfix

There was an issue with some of the features when a password protected VBA Project was loaded. This caused several of the tool window, most notably the Test Explorer, to fail to open. As many of the more popular add-ins are password protected, this was a pretty big problem.

This has been corrected and the 32bit installer is available now. This post will be updated when the 64bit installer is available.

Download the latest version here.

Overcoming Limitations: Testing that an event was raised

Why?

Rubberduck unit tests live in standard modules (.bas) for a reason: they are executed with Application.Run, which is meant to execute macros. In an ideal world, Rubberduck unit tests would live in class modules (.cls) and be executed with CallByName. The problem is that CallByName is a VBA function, not an API member – and we can’t just execute arbitrary VBA code magically, so until that’s figured out, we have a little bit of a limitation here.

On top of that, not all Office applications feature an Application.Run method, which means Rubberduck unit tests can’t work in, say, Outlook.

It also means Rubberduck unit tests can’t declare a WithEvents object variable, because like the Implements keyword, that keyword is reserved for use in class modules.


Now, what?

So if one has a class with a method that raises an event, a test that would ensure the event is raised with the expected parameters, needs to take a little detour.

Say we have MyClass looking like this:

Option Explicit
Public Event Foo(ByVal value As String)

Public Sub DoSomething(ByVal value As String)
 RaiseEvent Foo(value)
End Sub

We will want to write a test to verify that DoSomething raises event Foo with the specified value argument.

TestHelper class

First step is to declare a WithEvents object variable to handle the event. Since that can only be done in a class module, we’ll need a new class module – call it TestHelper.

Option Explicit
Private WithEvents sut As MyClass
Private RaisedCount As Long
Private LastValue As String

Public Property Get SystemUnderTest() As MyClass
 Set SystemUnderTest = sut
End Property

Public Property Set SystemUnderTest(ByVal value As MyClass)
 Set sut = value
End Property

Public Property Get HasRaisedEvent() As Boolean
 RaisedCount > 0
End Property

Public Property Get Count() As Long
 Count = RaisedCount
End Property

Public Property Get LastEventArg() As String
 LastEventArg = LastValue
End Property

Private Sub sut_Foo(ByVal value As String)
 RaisedCount = RaisedCount + 1
 LastValue = value
End Sub

Now that we have encapsulated the knowledge of whether and how our event was raised, we’re ready to write a test for it.

TestMethod

'@TestMethod
Public Sub TestMethod1() 'TODO: Rename test
 On Error GoTo TestFail
 
 'Arrange:
 Dim sut As New MyClass
 Dim helper As New TestHelper
 Set helper.SystemUnderTest = sut
 
 Const expected As String = "foo"
 
 'Act:
 sut.DoSomething expected

'Assert:
 Assert.IsTrue helper.HasRaisedEvent
 Assert.AreEqual expected, helper.LastEventArg

TestExit:
 Exit Sub

TestFail:
 Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
End Sub

This test will pass as is, and will fail when the tested method’s logic changes:

Public Sub DoSomething(ByVal value As String)
 RaiseEvent Foo(value & "A")
End Sub

test-failed


Until we figure out a way to move Rubberduck unit tests to class modules and run them with CallByName, using a helper class will be the only way to test event raising.