RD-VBA CLI

(English below)

Un jalon majeur a été franchi la semaine dernière : la plateforme RDCore dispose désormais d’un interpréteur qui assemble toutes les pièces du puzzle en quelque chose de traçable, configurable et extensible, qui ressemble aux débuts d’un runtime.

Là où tout ceci devient très concret : l’application console exécute maintenant une boucle REPL interactive où vous exécutez une session runtime en mode immédiat :

Ne vous laissez pas avoir par le look C64-BASIC : il s’agit d’un client LSP avec une session RD-VBA.

Les 4 octets alloués sont pris par le module et la procédure synthétique où vit le code source de votre programme. Tapez une instruction VBA, elle s’évalue et affiche son résultat; précédez-la d’un numéro de ligne, et vous venez d’inscrire cette instruction à cette ligne dans votre programme RD-VBA. Le nom de ce module synthétique est Program, et le nom de la procédure est Main. Vous verrez régulièrement ces identifiants dans les traces de la pile d’exécution (stack trace) des erreurs rencontrées – qui seront ici toujours sous Main, mais vous comprendrez que ce même mécanisme fonctionnera peu importe la profondeur de la pile dans un véritable client (IDE), avec un vrai projet et plusieurs modules et procédures.

L’idée ici n’est pas de faire évoluer cette application en un IDE non plus – mais ça fait une façon très divertissante de tester la plateforme, le cœur de langage et ses sémantiques. Le rôle de cette application est de simplement permettre de tester la plateforme de bout en bout. Étant un client LSP, nous verrons fort probablement apparaître un surlignage sémantique de la syntaxe au niveau de la sortie de LIST par exemple, ou alors des listes de complétion interactives; les diagnostics sont déjà branchés et ANALYZE en affichera les détails dès que ceux-ci deviendront disponibles. PEEK/POKE ne sont que pure lecture/écriture directe en mémoire à la Commodore64.

RUN exécute le programme de haut en bas, à moins que vous lui construisiez un parcours avec GoTo et GoSub/Return, auquel cas vous savez probablement ce que vous faites… sinon les diagnostics seront bientôt là pour vous guider.

Sémantiques

Toute la plomberie est en place; ce qu’il reste, c’est l’implémentation : les instructions I/O comme Open, Close, Print#, Write#, Put#, Get# sont implémentées; les déclarations, boucles, conditions, sauts et gestion d’erreurs sont fonctionnels. Beaucoup reste à faire: chacune des instructions doit avoir une implémentation, sans quoi les exécuter sera toujours en erreur.

L’implémentation de la librairie standard a débuté : Len et LenB sont les premiers consommateurs des données de stockage des UDT, et bien que LSet fonctionne tel que spécifié sur une String ou un UDT, la très vaste majorité de la librairie demeure à implémenter. Il s’agit de travail relativement simple qui n’a pas à traverser plusieurs couches d’abstraction et peuvent être des gains rapides; c’est pourquoi j’ai préféré donner les plus gros morceaux à Claude, les fonctions pures de la librairie peuvent être implémentées plus tard.

Ceci dit l’exécution et la gestion d’erreurs en place donnent déjà un bon aperçu :

Un programme RD-VBA qui ne contient pas d’instructions non-implémentées se complète.

Le retrait de l’instruction On Error GoTo révèle quelques faits intéressants :

D’abord, il y a une différence entre une erreur lancée explicitement par l’application, et une erreur d’exécution. Ensuite on remarque le numéro de ligne, puis la stack trace.

Ce que ces images ne montrent pas, c’est que le numéro de ligne est un symbole ici, seulement parce qu’il s’agit du mode programme – mais dans un document, la configuration par défaut fait de ce numéro l’emplacement L1C1 exact de l’erreur dans le document.

Une erreur d’application (VBA00000) est une erreur lancée par le code source (via Error, ou Err.Raise); une erreur d’exécution (VBR00000) est une erreur lancée par l’interpréteur. MS-VBA ne différencie pas réellement ces deux types d’erreurs – il y a donc désormais une distinction claire entre les deux, et ça ne brise rien du tout!

Aucune erreur d’application ne peut être confondue avec une erreur d’exécution, et il y aura toujours une trace de la pile d’exécution, même en mode immédiat!

Limitations

Certaines limitations sont inhérentes au fait qu’il s’agisse d’une application console: en effet il serait surprenant d’y voir une fonctionnalité affichant des info-bulles au survol d’un symbole.

D’autres limitations sont plutôt liées au design:

  • Le module Program et la procédure Main sont intrinsèques et ne peuvent pas être renommés.
  • Une commande sans numéro de ligne s’exécute immédiatement; les numéros de ligne sont donc obligatoires pour tout programme dans ce client.
  • Les numéros de lignes ne devraient pas empêcher l’utilisation de libellés, mais ceux-ci ne fonctionnent pas actuellement avec un numéro de ligne; GoTo/GoSub doivent donc référer à un numéro de ligne.
  • Les continuations sont invalides: un numéro de ligne ne peut pas être situé en plein milieu d’une instruction.
  • Limitation éventuelle du jeu de syntaxe valide en mode BASIC (configurable).
  • Limitation éventuelle à 10,000 lignes de la longueur permise d’une procédure; l’erreur procedure too long devrait alors être lancée à la compilation, mais possible que le mode BASIC fonctionne différemment.
  • Exit Sub termine le programme; Exit Function et Exit Property sont statiquement invalides.
  • Sub Main() et End Sub sont implicites: le corps du programme constitue le corps de cette procédure.
  • L’ajout de procédures additionnelles n’est pas supporté (les numéros de ligne étant invalides au niveau des déclarations d’un module); utilisez plutôt GoSub/Return avec de larges sauts de numérotation pour coder des sous-routines.

D’autres limitations sont ponctuelles et seront résolues avant le lancement de la v1 :

  • L’instruction REM qui explose à l’exécution est objectivement comique, mais ne restera vraisemblablement pas brisée longtemps.
  • L’invocation de membres de la librairie standard qui ne sont pas encore implémentés sera également en erreur.

Manipulation de Mémoire

Les commandes PEEK et POKE ne sont pas (encore?) implémentées en tant que mots-clés et donc pas utilisables dans du code source RD-VBA, mais les commandes fonctionnent et permettent donc une lecture/écriture directe d’octets en mémoire applicative – opération évidemment très risquée, mais la nostalgie l’a emportée.

Ceci dit les opérations LSet fonctionnent dans le code source sur des String et UDT.

Jalons

L’évolution du cœur de langage est complètement découplée de celle de l’application CLI, donc celle-ci fonctionnera de mieux en mieux et de façon de plus en plus complète à mesure que s’ajouteront les sémantiques du cœur de langage, et ce sans rien devoir corriger ou modifier dans l’application même.

Cela ne signifie pas pour autant que le travail est terminé!

L’application supporte notamment des thèmes, donc si le bleu C64-BASIC ne vous plaît pas, vous pouvez toujours passer à un thème foncé moderne. Ce n’est donc qu’une question de temps avant que la sortie de LIST affiche un surlignage sémantique issu du serveur de langage!

Exploiter cette connexion LSP constitue la façon dont cette application deviendra ultimement beaucoup plus qu’un éditeur BASIC. ANALYZE est déjà prêt à afficher les diagnostics, dès qu’ils commenceront à apparaître dans la plateforme.

Le mode REPL est bien pour une démo, mais le véritable outil est le mode CLI non interactif.

Les outils modernes s’exécutent dans des pipelines CI/CD (intégration et livraison continues) dans le cloud. rdc.exe sera à terme en mesure de compiler un projet, d’en exécuter les tests, et de bloquer une livraison qui dévie des conventions établies, le tout complètement en-dehors de l’écosystème MS-VBA.

Êtes-vous READY?


Mise à jour | Update 2026-10-02:

English

Si seulement 1986 avait eu LSP…
If only 1986 had LSP…

A major milestone was knocked down in the last week: the RDCore platform now has an interpreter that puts all the previous pieces together into a traceable, configurable, and extensible beginning of a runtime.

Where this gets very real, is with the CLI app now running an interactive REPL program mode where you’re basically running a runtime session in immediate mode:

Don’t be fooled by the C64-BASIC look; this CLI app is a full-fledged LSP client with an active RD-VBA runtime session.

The 4 bytes taken are allocated for the synthesized module and procedure scope your program space lives in. Type a VBA expression, and you get its result; precede it with a line number, and now you’ve written that instruction at that specified line number in your program. The name of this synthesized module is Program, and the name of the enclosing procedure is Main. This matters, because you’ll see these identifiers in error stack traces – here only ever inside Main, but you can see what this means for error handling with an actual IDE client, and a project with real procedure scopes.

The idea is not to evolve this app into one, either; it’s just to make a fun way to test the language core and semantics… and diagnostics – the platform, basically. The role of RD-VBA CLI mode is to test the platform end-to-end, hands-on – and being a LSP client means we’ll probably get to write old-school BASIC programs with semantic syntax highlighting in LIST outputs, completion lists for the REPL inputs. Diagnostics are already wired, ANALYZE will start showing meaningful output when providers/extensions issue diagnostics. PEEK/POKE is just C64-like pure unsafe program memory read/write.

RUN runs the program top to bottom, unless you make it do funny things like GoSub, in which case you probably know what you’re doing. If you don’t, …RDCore diagnostics will eventually have your back covered.

Semantics

So the plumbing is in place; what remains is semantics: file I/O statements like Open, Write, Print#, are implemented at the language core layer. Declarations, control flow, error handling as well. Still a lot remains TODO – every single statement keyword must have its semantics implemented, otherwise running them gets you an internal error.

Standard library implementation has begun as well; Len and LenB are the first consumers of the UDT member storage data, and while all the symbols are defined and LSet works as specified on UDT and strings, the vast majority of the standard library remains to be implemented; this is mechanical work that doesn’t need to reach across multiple layers to be a quick gain – so I let Claude/Opus focus on the more meaty parts that do cross into the runtime and memory layout, leaving the simpler, pure functions for later.

Control flow and error handling is fully functional:

BASIC-like RD-VBA code runs end-to-end in the app, …as long as the program doesn’t involve unimplemented semantics.

Removing the On Error GoTo instruction reveals a number of interesting facts about RD-VBA:

First, there’s a difference between an error you raise and an error the runtime raises. Second, errors have a source location pointing to the line number of the faulting instruction, and then there’s a stack trace.

What this doesn’t tell you, is that the platform is showing symbolic line numbers here only because it has no workspace document proper (the internal buffer just simulates one), but in an IDE client the default configuration would be reporting the actual L1C1 document location.

An application error (“VBA00000”) is a run-time error that is raised by the workspace code; a runtime error (“VBR00000”) is a run-time error that is raised in the language core runtime semantics. MS-VBA historically did not really differentiate between these errors; now there’s a structural distinction between them, and it doesn’t break anything!

No Error or Err.Raise statement can ever be mistaken for a runtime error, and we always get a stack trace, even in immediate command mode.

Limitations

Some limitations are inherent to the CLI app being a CLI app: hover tooltips aren’t going to be available, and that’s a given.

Others are design decisions. These include:

  • Program.Main project and procedure scope; intrinsic, cannot be renamed.
  • C64-style program interface, using line numbers as markers for instructions that are stored in the source buffer rather than immediately piped to the interpreter; this makes line numbers inherently mandatory in this client.
  • Line numbers should not prevent the use of line labels, however labels do not currently parse with line numbers. GoTo must therefore refer to line numbers.
  • Line continuations cannot be used; a line number isn’t a valid statement to continue into.
  • Eventually artificially limiting the valid RD-VBA syntax (including comments) in CLI program mode to the BASIC subset of VB, via appsettings.json platform configuration.
  • Eventually limiting the number of logical lines to 10,000; procedure too long compile-time error ought to be raised then, but BASIC mode will probably end up working differently.
  • Exit Sub terminates the program; Exit Function and Exit Property are statically illegal.
  • Sub Main() and End Sub are implicit; the program body is that procedure’s body.
  • Additional Sub procedures cannot be defined (line numbers are illegal on declaration members); use GoSub/Return with wide line numbering gaps to denote subroutines instead.

Other limitations are temporary pre-alpha missing implementations that will definitely be implemented before a v1.0 can ship:

  • REM statements (BASIC-style comments) throwing statement not implemented is objectively funny, but will probably not take too long to get working as the no-op instruction it is.
  • Standard library implementation being incomplete, any function call that refers to a symbol defined there throws not implemented as well.

Unsafe Program Memory Ops

Not (yet?) implemented as keywords, the PEEK and POKE immediate commands allow byte-level unsafe reads and writes (!) from/to a specified address in program memory space. This is obviously dangerous and may lead to crashes, but nostalgia won.

That said LSet and RSet semantics are both implemented, including against UDT values.

🎯 Milestones

The evolution of the RD-VBA language core and runtime implementation is architecturally decoupled from the CLI application, so it’ll automatically throw fewer errors as the language core progresses, without fixing or changing anything anywhere in the CLI app.

That doesn’t mean the work is done, however.

The app supports themes, so if you don’t like C64-style BASIC blue, you might like a modern dark theme instead. It’s only a matter of time before LIST output gets semantic syntax highlighting from the language server!

Leveraging this LSP connection is how the app will turn into something more than a BASIC console. ANALYZE is already wired up to report diagnostics as soon as they start coming.

A BASIC immediate/command/program mode is nice for a platform demo, but the real tool is the non-interactive CLI mode.

Modern tools run in the cloud, from CI/CD scripts that build a project, run its tests, inspect its vulnerabilities and deviations from established coding practices; rdc.exe supports an extensible set of command-line tools exactly for this. Extensions can supply a provider for various commands such as test or analyze, and a CI/CD script could be configured to gate a build on tests passing and diagnostics showing no warnings or errors.

Are you READY?

Coming soon, in Rubberduck 2.2

The last “green” release was a couple of months ago already – time to take a step back, look at all we’ve done, and call it a “minor” update.

What’s up duck?

Functionality-wise, not much. Bug fixes, yes; this means fewer inspection false positives, fewer caching accidents, overall more stable usage. But this time some serious progress was also made in the COM & RCW management area, and Rubberduck 2.2 no longer crashes on exit, or leave a dangling host process, or brick the VBE on reload. Some components are still stubbornly refusing to properly release, so unload+reload is still a not-recommended thing to do, but doing so no longer causes access violations. Which is neat, because this particular problem had been plaguing Rubberduck since the early days of 2.0.

Source Control Disintegration

If you haven’t been following the project since v2.1 was released, you may be disappointed to learn that we are officially dropping the source control integration feature. Not saying it’ll never resurface, but the feature was never really stable, and rather than drain our limited resources on a nice but non-essential feature, we focused on the “core” stuff for now. So instead of keeping the half-baked, half-broken thing in place, we removed it – entirely, so there’s 0 chance any part of it interferes with anything else (there were hooks in place, handling parser state changes and some VBE events).

The “Export Project” functionality remains though, so you can still use your favorite source control provider (Git, SVN, Mercurial, etc.) – Rubberduck just isn’t providing a UI to wrap that provider’s functionality anymore.

Shiny & New

We have new inspections! Rubberduck can now tell you when a Case block is semantically unreachable. Or when For loops specify a redundant Step 1, or if you prefer having an explicit Step clause everywhere, it can tell you about that too. Another inspection warns about error-handling suppression (On Error Resume Next) that is never restored (On Error GoTo 0). If you’re unfortunate enough to encounter the thoroughly evil Def[Type] statements, you’ll be relieved to know that Rubberduck will now warn you about implicitly typed identifiers.

Code Metrics is an entirely new tool, that evaluates cyclomatic complexity and nesting levels of each method and module. The feature clearly needs some UI work (wink wink, nudge nudge, C#/WPF reader), and enhancement ideas are always welcome.

The unit test execution engine no longer invokes the host application. There’s a bit of black magic going on here, but to keep it simple, the unit testing feature now works in every single VBE host application.

But the most spectacular changes aren’t really tangible, user-facing things. We’ve streamlined settings, upgrated our grammars from Antlr4.3 to Antlr4.6 – which fixed a number of parser issues, including significant performance improvements when parsing long Boolean expressions; the IInspection interface was fine-tuned again, COM object references were removed in a number of critical places. If you have a fork of the project, you already know that we’ve split Rubberduck.dll into Rubberduck.Core.dll and Rubberduck.Main.dll, with the entry point and IoC configuration in ‘Main’.

Oh, I lied. One of the most spectacular changes is a tangible, user-facing thing. It’s just not exactly in the main code base, is all. Poor installer, always gets left behind.

Administrative Privileges no longer needed!

Since a couple of pre-release builds, the Rubberduck installer supports per-user installs that no longer require admin privs. This means Rubberduck can now be installed on a locked-down workstation, without requiring IT intervention! This revamped installer also detects and properly uninstalls a previous Rubberduck install (admin elevation would be required to uninstall a per-machine installation of a previous build though), so manually uninstalling through the control panel before upgrading, is no longer recommended/needed. Doesn’t hurt, but shouldn’t change anything, really.

The “installating / instructions” and “contributing / initial setup” wiki pages have been updated accordingly on GitHub.

This new installer no longer assumes Microsoft Office is present, and registers for both 32 and 64-bit host applications.


That’s it? What happened to the rest of 2.1.x?

I did say “minor update”, yeah? The previously announced roadmap for 2.1.x was too ambitious, and not much of it is shipping in this release. In fact, that roadmap should have said “2.x”… versioning is hard, okay? If we stuck to 2.1.x, then a v2.2 would have been moot, since by then we would have had much of 3.0 in place.

Anyway, 2.2 is a terrific improvement over 2.1, on many levels – and that can only mean one thing: that the current development cycle will inevitably lead to even more awesomeness!

RD2018

Rubberduck 2.1.x

The release was going to include a number of important fixes for the missing annotation/attribute inspection and quick-fix, but instead we disabled it, along with a few other buggy inspections, and pushed the release – 7 months after 2.0.13, the last release was now over 1,300 commits behind, and we were reaching a point where we knew a “green release” was imminent, but also a point where we were going to have to make some more changes to parts of the core – notably in order to implement the fixes for these broken annotation/attribute inspections.

So we shipped what we had, because we wouldn’t jeopardize the 2.1 release with parser logic changes at that point.

Crossroads

wooden_signpost_at_the_crossroads1
By Hillebrand Steve, U.S. Fish and Wildlife Service [Public domain], via Wikimedia Commons
So here we are, at the crossroads: with v2.1.0 released, things are going to snowball – there’s a lot on our plates, but we now have a solid base to build upon. Here’s what’s coming:

  • Castle Windsor IoC: hopefully-zero user-facing changes, we’re replacing good old Ninject with a new dependency injection framework in order to gain finer control over object destruction – we will end up correctly unloading!

That’s actually priority one: the port is currently under review on GitHub, and pays a fair amount of long-standing technical debt, especially with everything involving menus.

  • Annotation/Attributes: fixing these inspection, and the quick-fix that synchronizes annotations with module attributes and vice-versa, will finally expose VB module and member attributes to VBA code panes, using Rubberduck’s annotation syntax.

For example,  adding '@Description("This procedure does XYZ") on top of a procedure will tell Rubberduck that you mean that procedure to have a VB_Description attribute; when Rubberduck parses that module after you synchronize, it will be able to use that description in the context status bar, or as tooltips in the Code Explorer.

This is considered a serious issue, because it affects pretty much every single inspection. Luckily there’s a [rather annoying and not exactly acceptable] work-around (apply the fix bottom-to-top in a module), but still.

But there’s a Greater Picture, too.

The 2.1.x Cycle

At the end of this development cycle, Rubberduck will:

  • Work in the VB6 IDE;
  • Have formalized the notion of an experimental feature;
  • Have a working Extract Method refactoring;
  • Make you never want to use the VBE’s Project References dialog ever again;
  • Compute and report various code metrics, including cyclomatic complexity and nesting levels, and others (and yes, line count too);
  • Maybe analyze a number of execution paths and implement some of the coolest code inspections we could think of;
  • Be ready to get really, really serious about a tear-tab AvalonEdit code pane.

If all you’re seeing is Rubberduck’s version check, the next version you’ll be notified about will be 2.1.2, for which we’re shooting for 2017-11-13. If you want to try every build until then (or just a few), then you’ll want to keep an eye on our releases page!

Go ahead, mock VBA

Rubberduck has been offering IDE-integrated unit test since day one.

But let’s face it: unit testing is hard. And unit testing VBA code that pops a MsgBox isn’t only hard, it’s outright impossible! Why? Because it defeats the purpose of an automated test: you don’t want to be okaying message boxes (or worse, clicking No when the test needed you to click Yes), you want to run the tests and watch them all turn green!

So you had to implement some kind of wrapper interface, and write code that doesn’t call MsgBox directly – like the D of SOLID says, depend on abstractions, not on concrete types.

So you’d code against some IMsgBox wrapper interface:

Option Explicit
Public Function Show(ByVal prompt As String, _
 Optional ByVal buttons As VbMsgBoxStyle = vbOKOnly, _
 Optional ByVal title As String = vbNullString, _
 Optional ByVal helpFile As String, _
 Optional ByVal context As Long) As VbMsgBoxResult
End Function

And then you’d implement the concrete type:

Option Explicit
Implements IMsgBox
Private Function IMsgBox_Show(ByVal prompt As String, _
 Optional ByVal buttons As VbMsgBoxStyle = vbOKOnly, _
 Optional ByVal title As String = vbNullString, _
 Optional ByVal helpFile As String, _
 Optional ByVal context As Long) As VbMsgBoxResult
    IMsgBox_Show = MsgBox(prompt, buttons, title, helpFile, context)
End Function

Now that gets you compilable VBA code, but if you want to write a test for code where the result of a MsgBox call can influence the tested method’s code path, you need to make a fake implementation, and inject that FakeMsgBox into your code, so that your code calls not the real MsgBox function, but the fake implementation.

And if you want to verify that the code setup a vbYesNo message box with the company name as a title, you need to adapt your fake message box and make it configurable.

In other words, setting up fakes by hand is a pain in the neck.

So this is where Rubberduck tests are going:

'@TestMethod
Public Sub TestMethod1()
    On Error GoTo TestFail
    
    Fakes.MsgBox.Returns 42
    Debug.Print MsgBox("Flabbergasted yet?", vbYesNo, "Rubberduck") 'prints 42
    
    With Fakes.MsgBox.Verify
        .Parameter "prompt", "Flabbergasted yet?"
        .Parameter "buttons", vbYesNo
        .Parameter "title", "Rubberduck"
    End With
TestExit: 
    Exit Sub
TestFail: 
    Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
End Sub

Soon. Very soon. Like, next release soon, Rubberduck will begin to allow unit test code to turn the actual MsgBox into a fake one, by setting up a Rubberduck fake.

So yeah, we’re mocking VBA. All of it.

To Be Continued…

So, 2.0.12 is late… what’s cooking?

Recently I tweeted this:

The release of Rubberduck 2.0.12, due 5 days ago, is being delayed because we have something awesome cooking up. Give us 2-3 more weeks 🙂

TL;DR: if awesomeness can be cooked, that’s what’s cooking.

The amount of work that went into the upcoming release is tremendous. We’ve been trying to figure out exactly what was blowing up when the VBE dismantled itself and the host was shutting down, causing that pesky crash on exit… ever since we’ve introduced WPF user controls in dockable toolwindows. And at last, solved it.

We’ve been working on improving performance and thread safety of the entire parsing engine, and fixed a few grammar/parser bugs on the way, including a long-standing bug that made redundant parentheses trip a parse exception, another with the slightly weird and surely redundant Case Is = syntax, and @Magic annotations can now legally be followed by any comment, which is useful when you want to, well, annotate an annotation:

'@Ignore ProcedureNotUsed; called by [DoSomething] button on Sheet12
Public Sub DoSomething()
    ...
End Sub

We’ve enhanced the COM reference collector such that the resolver has every bit of useful information about everything there is to know in a type library referenced by a VBA project. This allows us to enhance other features, like the context-sensitive commandbar that tells you what Rubberduck is your selection as, e.g. a TextBox control in a UserForm:

textbox

(don’t mind that “Serialize” button – it’s only there in debug builds ;^)

Oh, and then there’s the interactions with the website – we’ll be running the inspections and the indenter on the website, and we’ll have the ability to (optionally) have Rubberduck know when a new version is available!


2.0.12 is going to be epic.

The 2.0 build

And then there’s even more: we’re going to make the inspections a concern of the parser engine, and turn them into parse tree node annotations – which means the code that currently finds the Declaration that’s currently selected (or one of its references), can also be used to find inspection results associated with that particular Declaration; this will probably prompt a redesign of how we present inspection results, and will definitely improve performance and memory footprint.

One of the best 2.x features is probably going to be the add/remove references dialog, which is currently merely prototyped. Beefing up unit testing with data-driven tests is also going to be a big one.

And when you see where we want to be for 3.0 (code path analysis & expression resolution, plug-in architecture, a subclassed CodePanethat actually tells us what’s going on, perhaps even with our own enhanced IntelliSense, more host-specific behaviors, TONS of new inspections), …this project is so awesome, I could just keep going on and on.

Not coming soon enough? I know, right!

cr-ducky-great-again-600x500.

To be continued…

 

Nothing to declare

Somewhere in the first batch of issues/to-do’s we created when we started Rubberduck on GitHub (Issue# 33 actually), there was the intention to create a tool that could locate undeclared variables, because even if you and I use Option Explicit and declare all our variables, we have brothers and sisters that have to deal with code bases that don’t.

So we tried… but Rubberduck simply couldn’t do this with the 1.x resolver: identifiers that couldn’t be resolved were countless, running an inspection that would pop a result for every single one of them would have crippled our poor little duckling… so we postponed it.

The 2.0 resolver however, thinks quite literally like VBA itself, and knows about all available types, members, globals, locals, events, enums and whatnot, not just in the VBA project, but also in every referenced COM library: if something returns a type other than Variant or Object, Rubberduck knows about it.

The role of the resolver is simple: while the parse tree of a module is being traversed, every time an identifier is encountered it attempts to determine which declaration is being referred to. If the resolver finds a corresponding declaration, an IdentifierReference is created and added to the Declaration instance. And when the resolver can’t resolve the identifier (i.e. locate the exact declaration the identifier is referring to), a null reference was returned and, unless you have detailed logging enabled, nothing notable happens.

As of the last build, instead of “doing nothing” when a reference to variable can’t be resolved to the declaration of that variable, we create a declaration on the spot: so the first appearance of a variable in an executable statement becomes the “declaration”.

We create an implicit Variant variable declaration to work with, and then this happens:

hhp2m

With a Declaration object for an undeclared variable, any further reference to the same implicit variable would simply resolve to that declaration – this means other Rubberduck features like find all references and refactor/rename can now be used with undeclared variables too.

Rubberduck is now seeing the whole picture, with or without Option Explicit.

The introduce local variable quick-fix simply inserts a “Dim VariableName As Variant” line immediately above the first use in the procedure, where VariableName is the unresolved identifier name. The variable is made an explicit Variant, …because there’s another inspection that could fire up a result if we added an implicit Variant.

The quick-fix doesn’t assume an indentation level – makes me wonder if we should run the indenter on the procedure after applying a quick-fix… but that’s another discussion.

To be continued…

VBA Rubberducking (Part 2)

This post is the second in a series of post that walk you through the various features of the Rubberduck open-source VBE add-in. The first post was about the navigation features.

Code Inspections

vbe

Back when the project started, when we started realizing what it meant to parse VBA code, we knew we were going to use that information to tell our users when we’re seeing anything from possibly iffy to this would be a bug in their code.

The first one to be implemented was OptionExplicitInspection. The way Rubberduck works, a variable that doesn’t resolve to a known declaration simply doesn’t exist. Rubberduck is designed around the fact that it’s working against code that VBA compiles; is also needs to assume you’re working with code that declares its variables.

Without ‘Option Explicit’ on, Rubberduck code inspections can yield false positives.

Because it’s best-practice to always declare your variables, and because the rest of Rubberduck won’t work as well as it should if you’re using undeclared variables, this inspection defaults to Error severity level.

OptionExplicitInspection was just the beginning. As of this writing, we have implementations for 35 inspections, most with one or more one-click quick-fixes.

35 inspections?

And there’s a couple more left to implement, too. A lot of inspections rely on successful parsing and processing of the entire project and its references; if there’s a parsing error, then Rubberduck will not produce new inspection results. When parsing succeeds, inspections run automatically and the “status bar” indicates Ready when it’s completed.

  1. AssignedByValParameterInspection looks for parameters passed by value and assigned a new value, suggesting to either extract a local variable, or pass it by reference if the assigned value is intended to be returned to the calling code.
  2. ConstantNotUsedInspection looks for constant declarations that are never referenced. Quick-fix is to remove the unused declaration.
  3. DefaultProjectNameInspection looks for unnamed projects (“VBAProject”), and suggests to refactor/rename it. If you’re using source control, you’ll want to name your project, so we made an inspection for it.
  4. EmptyStringLiteralInspection finds “” empty strings and suggests replacing with vbNullString constants.
  5. EncapsulatePublicFieldInspection looks for public fields and suggests making it private and expose it as a property.
  6. FunctionReturnValueNotUsedInspection locates functions whose result is returned, with none of the call sites doing anything with it. The function is used as a procedure, and Rubberduck suggests implementing it as such.
  7. IdentifierNotAssignedInspection reports variables that are declared, but never assigned.
  8. ImplicitActiveSheetReferenceInspection is Excel-specific, but it warns about code that implicitly refers to the active sheet.
  9. ImplicitActiveWorkbookReferenceInspection is also Excel-specific, warns about code that implicitly refers to the active workbook.
  10. ImplicitByRefParameterInspection parameters are passed by reference by default; a quick-fix makes the parameters be explicit about it.
  11. ImplicitPublicMemberInspection members of a module are public by default. Quick-fix makes the member explicitly public.
  12. ImplicitVariantReturnTypeInspection a function or property getter’s signature doesn’t specify a return type; Rubberduck makes it return a explicit Variant.
  13. MoveFieldCloserToUsageInspection locates module-level variables that are only used in one procedure, i.e. its accessibility could be narrowed to a smaller scope.
  14. MultilineParameterInspection finds parameters in signatures, that are declared across two or more lines (using line continuations), which hurts readability.
  15. MultipleDeclarationsInspection finds instructions containing multiple declarations, and suggests breaking it down into multiple lines. This goes hand-in-hand with declaring variables as close as possible to their usage.
  16. MultipleFolderAnnotationsInspection warns when Rubberduck sees more than one single @Folder annotation in a module; only the first annotation is taken into account.
  17. NonReturningFunctionInspection tells you when a function (or property getter) isn’t assigned a return value, which is, in all likelihood, a bug in the VBA code.
  18. ObjectVariableNotSetInspection tells you when a variable that is known to be an object type, is assigned without the Set keyword – this is a bug in the VBA code, and fires a runtime error 91 “Object or With block variable not set”.
  19. ObsoleteCallStatementInspection locates usages of the Call keyword, which is never required. Modern form of VB code uses the implicit call syntax.
  20. ObsoleteCommentSyntaxInspection locates usages of the Rem keyword, a dinosaurian syntax for writing comments. Modern form of VB code uses a single quote to denote a comment.
  21. ObsoleteGlobalInspection locates usages of the Global keyword, which is deprecated by the Public access modifier. Global cannot compile when used in a class module.
  22. ObsoleteLetStatementInspection locates usages of the Let keyword, which is required in the ancient syntax for value assignments.
  23. ObsoleteTypeHintInspection locates usages of type hints in declarations and identifier references, suggesting to replace them with an explicit value type.
  24. OptionBaseInspection warns when a module uses Option Base 1, which can easily lead to off-by-one bugs, if you’re not careful.
  25. OptionExplicitInspection warns when a module does not set Option Explicit, which can lead to VBA happily compiling code that uses undeclared variables, that are undeclared because there’s a typo in the assignment instruction. Always use Option Explicit.
  26. ParameterCanBeByValInspection tells you when a parameter is passed ByRef (implicitly or explicitly), but never assigned in the body of the member – meaning there’s no reason not to pass the parameter by value.
  27. ParameterNotUsedInspection tells you when a parameter can be safely removed from a signature.
  28. ProcedureCanBeWrittenAsFunctionInspection locates procedures that assign a single ByRef parameter (i.e. treating it as a return value), that would be better off written as a function.
  29. ProcedureNotUsedInspection locates procedures that aren’t called anywhere in user code. Use an @Ignore annotation to remove false positives such as public procedures and functions called by Excel worksheets and controls.
  30. SelfAssignedDeclarationInspection finds local object variables declared As New, which (it’s little known) affects the object’s lifetime and can lead to surprising/unexpected behavior, and bugs.
  31. UnassignedVariableUsageInspection locates usages of variables that are referred to before being assigned a value, which is usually a bug.
  32. UntypedFunctionUsageInspection recommends using String-returning functions available, instead of the Variant-returning ones (e.g. Mid$ vs. Mid).
  33. UseMeaningfulNamesInspection finds identifiers with less than 3 characters, without vowels, or post-fixed with a number – and suggests renaming them. Inspection settings will eventually allow “white-listing” common names.
  34. VariableNotAssignedInspection locates variables that are never assigned a value (or reference), which can be a bug.
  35. VariableNotUsedInspection locates variables that might be assigned a value, but are never referred to and could be safely removed.
  36. VariableTypeNotDeclaredInspection finds variable declarations that don’t explicitly specify a type, making the variable implicitly Variant.
  37. WriteOnlyPropertyInspection finds properties that expose a setter (Property Let or Property Set), but no getter. This is usually a design flaw.

Oops, looks like I miscounted them… and there are even more coming up, including host-specific ones that only run when the VBE is hosted in Excel, or Access, or whatever.

The Inspection Results toolwindow

If you bring up the VBE in a brand new Excel workbook, and then bring up the inspection results toolwindow (Ctrl+Shift+I by default) you could be looking at something like this:

8owq4

Most inspections provide one or more “quick-fixes”, and sometimes a quick-fix can be applied to all inspection results at once, within a module, or even within a project. In this case Option Explicit can be automatically added to all modules that don’t have it in the project, using the blue Fix all occurrences in project link at the bottom.

Or, use the Fix drop-down menu in the top toolbar to apply a quick-fix to the selected inspection result:

fdqhe

Each inspection has its own set of “quick-fixes” in the Fix menu. A common one is Ignore once; it inserts an @Ignore annotation that instructs the specified inspection to skip a declaration or identifier reference..

InspectionResults

The bottom panel contains information about the selected inspection result, and fix-all links that always use the first quick-fix in the “Fix” menu. Disable this inspection turns the inspection’s “severity” to DoNotShow, which effectively disables the inspection.

You can access inspection settings from the Rubberduck | Settings menu in the main commandbar, or you can click the settings button in the inspection results toolwindow to bring up the settings dialog:

wqtok

If you like using the Call keyword, you can easily switch off the inspection for it from there.

The Copy toolbar button sends inspection results into the clipboard so they can be pasted into a text file or an Excel worksheet.

As with similar dockable toolwindows in Rubberduck, the way the grid regroups inspection results can be controlled using the Grouping menu:

54ur0

The refresh button causes a re-parse of any modified module; whenever parser state reaches “ready”, the inspections run and the grid refreshes – just as it would if you refreshed from the Rubberduck command bar, or from the Code Explorer toolwindow.

To be continued…

 

 

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.

An update on the v1.3 release

As I said earlier on Twitter, I messed up the 1.3 release… big time. Shooting for April 18 was fine, rushing to meet that “deadline” wasn’t.

We’re removing the 1.3 pre-release. I pushed “release early, release often” too far this time. Recommend uninstall+rollback to 1.22.. sorry!

Again, apologies. I was so excited about releasing all the features I had been working on for over a month now, that I overlooked a number of serious issues, and to top it off, I blew up the x64 installer… again.

We’re currently fixing those last few issues, and we’ll take the time it takes to test things properly, and release a stable v1.3 that everybody will enjoy using.

Stay tuned, v1.3 is coming… and you’re going to love it.