Recent Entries 10
- pattern minor 112d agoRubberduck Test Module CreationI recently started learning C# and am taking the opportunity to try and accelerate my introduction by helping the RubberDuck team (Website and GitHub Repo). The full class code that I'm working on can be found here. Its purpose is to create a Module in VBA that will allow unit testing. - Since `FakesFieldDeclarationFormat` and `AssertFieldDeclarationFormat` are used only once would it be better to move them to just above where they are fed into `DeclarationFormatFor`? - Would it be better to migrate `FolderAnnotation` into `TestModuleEmptyTemplate` since when `formattedModuleTemplate` is created that is ultimately what is happening? - Does anyone have any suggestions for improving the names? - Anything else that help improve the code and that may assist me in learning is welcome. The main code in question is below. ` private const string TestModuleEmptyTemplate = "'@TestModule\r\n{0}\r\n{1}\r\n{2}\r\n\r\n"; private const string FolderAnnotation = "'@Folder(\"Tests\")\r\n"; private const string FakesFieldDeclarationFormat = "Private Fakes As {0}"; private const string AssertFieldDeclarationFormat = "Private Assert As {0}"; private readonly string _moduleInit = string.Concat( "'@ModuleInitialize\r\n", "Public Sub ModuleInitialize()\r\n", $" '{RubberduckUI.UnitTest_NewModule_RunOnce}.\r\n", " {0}\r\n", " {1}\r\n", "End Sub\r\n\r\n", "'@ModuleCleanup\r\n", "Public Sub ModuleCleanup()\r\n", $" '{RubberduckUI.UnitTest_NewModule_RunOnce}.\r\n", " Set Assert = Nothing\r\n", " Set Fakes = nothing\r\n", "End Sub\r\n\r\n" ); private readonly string _methodInit = string.Concat( "'@TestInitialize\r\n" , "Public Sub TestInitialize()\r\n" , " '", RubberduckUI.UnitTest_NewModule_RunBeforeTest, ".\r\n" , "End Sub\r\n\r\n" , "'@TestCleanup\r\n" , "Public Sub TestCleanup()\r\n"
- pattern minor 112d agoProperly destroying the VBEThe disposable wrappers worked exactly as intended... and that turned out being a huge mistake: That's because the .net runtime creates a Runtime Callable Wrapper per-type, not per-instance*; it took a number of refactorings and adjustments just to be able to get the project to build and run, and then over 900 tests were broken and my life was a nightmare. Then I had an idea: since .net wouldn't let me destroy objects that were otherwise ready to be collected, what if I ditched `IDisposable` and moved on to a hierarchical teardown strategy? Enter `ISafeComWrapper`: ``` namespace Rubberduck.VBEditor.SafeComWrappers { public interface ISafeComWrapper { /// /// Releases all COM objects. /// void Release(); } } ``` In the `Extension` class (which you may recall seeing here), we `Release` the entire VBE object graph, like this: ``` private void ShutdownAddIn() { if (_app != null) { _app.Shutdown(); _app = null; } if (_kernel != null) { _kernel.Dispose(); _kernel = null; } _ide.Release(); _isInitialized = false; } ``` So here's the `SafeComWrapper` base class: ``` using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace Rubberduck.VBEditor.SafeComWrappers { public abstract class SafeComWrapper : ISafeComWrapper, IEquatable> where T : class { protected SafeComWrapper(T comObject) { _comObject = comObject; } public abstract void Release(); private readonly T _comObject; public T ComObject { get { return _comObject; } } public bool IsWrappingNullReference { get { return _comObject == null; } } protected TResult InvokeResult(Func member) { try { return member.Invoke(); } catch (COMException exception) { throw new Wrapper
- pattern minor 112d agoA generic IEnumerator to enumerate COM collectionsAs I wrapped the VBIDE API, I encountered a number of "collection types" (`Windows`, `CodePanes`, `VBComponents`, `VBProjects`, `References`, etc.) - these types implement the non-generic `IEnumerable` interface, so if I wanted to be able to iterate my wrappers like this for example: ``` using (var projects = _vbe.VBProjects) { foreach (var project in projects.Where(p => p.Protection == ProjectProtection.Unprotected)) { yield return project.Name; } } ``` ...then I was going to be needing some custom `IEnumerator` implementation, because even if I could explicitly `.Cast()` every time, at runtime the cast would fail because the enumerator yields a COM object, not a wrapper type. So I made a generic `ComWrapperEnumerator`, where `TComCollection` is the collection type (must implement `IEnumerable`) and `TWrapperItem` is the type of the `SafeComWrapper` to be created and returned - am I doing this right? I don't like the way I'm assuming the constructor only has a single `T` parameter (where `T` is the COM type being wrapped by `TWrapperItem`), is there a better way? ``` using System; using System.Collections; using System.Collections.Generic; namespace Rubberduck.VBEditor.DisposableWrappers { public class ComWrapperEnumerator : IEnumerator where TComCollection : IEnumerable { private readonly IEnumerator _internal; public ComWrapperEnumerator(TComCollection items) { _internal = items.GetEnumerator(); } public void Dispose() { var disposable = _internal as IDisposable; if (disposable == null) { return; } disposable.Dispose(); } public bool MoveNext() { return _internal.MoveNext(); } public void Reset() { _internal.Reset(); } public TWrapperItem Current { get { return (TWrapperItem)Act
- pattern minor 112d agoWrapping COM objects with IDisposableOne of the things believed to contribute to destabilizing Rubberduck 2.x, is the fact that a lot of COM object references are stored in many places, and `Marshal.ReleaseComObject` is never called for those, which is what the alleged problem would be. To fix this, I've undertaken the task of completely wrapping the VBIDE API with managed types that implement `IDisposable` and call `Marshal.ReleaseComObject` when disposing. This presented several problems: - The wrapped object reference might be `null`; but wrapping a `null` reference means things like `if (wrapper == null)` wouldn't behave properly; hence, the wrappers should override `==` and `!=` operators to make null-checks work more intuitively. - The COM threading model (STA) isn't completely compatible with .NET's (MTA); although in theory all managed calls get marshaled into a STA call, in practice the managed RCW could be "disconnected" from the underlying COM object, which basically means literally anything can throw a `COMException` - including a simple getter read. Wrapping everything in `try/catch` blocks would make for excessively redundant code, hence a generic invocation mechanism was implemented, to invoke a COM object's member while catching a `COMException`. - Because the managed code implements `IDisposable`, we'll want an `ObjectDisposedException` to be thrown whenever a member is accessed on a disposed object. Hence, a general-purpose "throw if disposed" mechanism was implemented, to simplify the implementations. Here is the base class from which all wrappers will be derived: ``` using System; using System.Runtime.InteropServices; namespace Rubberduck.VBEditor.DisposableWrappers { public abstract class WrapperBase : IDisposable where T : class { private readonly T _item; private bool _isDisposed; protected WrapperBase(T item) { _item = item; } protected internal T Item { get {
- pattern minor 112d agoIDTExtensibility2 implementation for Rubberduck's entry pointHere is the new & improved Rubberduck 2.x entry point class, based on MZ-Tools 8.0's `Connect.VBA` implementation of the `IDTExtensibility2` COM interface, graciously shared to the Rubberduck team by Mr. MZ-Tools himself, Carlos Quintero. The modifications made are not yet merged into the project's main repository at the time of this writing, but you can view it on commit 8ac768d9 in my own fork. I've yet to shim the add-in to get it to be a good citizen and run in its own dedicated `AppDomain` (.net creates RCW's per-AppDomain, that's why it does matter), so there are still a number of issues beyond just the "entry point" to completely stabilize Rubberduck 2.x, but for now I'm interested in feedback specifically on the `IDTExtensibility2` implementation - a useful add-in, I'm discovering, is much trickier to "do right" than what the "hello world" tutorials imply. Also, since we're using ninject for dependency injection, I'm also interested in feedback on the composition root here, the handling of the `_kernel` instance and, given the various different ways an add-in can be loaded/unloaded, whether this (which seems to work fine) is done right. Of course, any other feedback is welcome, too. ``` using Extensibility; using Microsoft.Vbe.Interop; using Ninject; using Ninject.Extensions.Factory; using Rubberduck.Root; using Rubberduck.UI; using System; using System.ComponentModel; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Windows.Threading; using Ninject.Extensions.Interception; using NLog; using Rubberduck.Settings; using Rubberduck.SettingsProvider; namespace Rubberduck { /// /// Special thanks to Carlos Quintero (MZ-Tools) for providing the general structure here. /// [ComVisible(true)] [Guid(ClassId)] [ProgId(ProgId)] [EditorBrowsable(EditorBrowsableState.Never)] // ReSharper disable once InconsistentNaming // note: under
- pattern minor 112d agoFighting f̶i̶r̶e̶ regex with f̶i̶r̶e̶ regexSo someone requested a Regex Builder / Assistant for Rubberduck and since my CS class had been covering the topic only recently I was thinking to myself: "This sounds interesting, you should do this". Long story short, a day of designing and a weekend of implementation (and some more polishing) as well as a few reviews later Rubberduck has a fully working Regex Assistant (which even supports i18n). It can be given a VBA Regular Expression pattern and tells you what it does in a manner similar to other well-known assistants (regex101, expresso, ...) This means it can parse a given syntactically correct Regex and recognize the separate Atoms and Quantifiers. It analyzes said Atoms and builds a structure that allows displaying useful information about the pattern. This is accomplished by parsing the expression into a Tree-Structure. Any Regular Expression is built by either the smallest possible unit you have (an Atom) or other regular expressions. This definition allows us to "abuse" the syntax into building our Design. We basically need to handle two (well three) things to represent any arbitrary Regular Expression. - Atoms - Regular Expressions - Quantifiers to be fully correct, Quantifiers actually belong in cahoots with Atoms, but they're.. different, so we'll treat them differently. This sets the stage for our "data holders": Atom.cs ``` using Rubberduck.RegexAssistant.i18n; using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace Rubberduck.RegexAssistant { public interface IAtom : IDescribable { string Specifier { get; } } internal class CharacterClass : IAtom { public static readonly string Pattern = @"(?.*?)(? _characterSpecifiers; public IList CharacterSpecifiers { get { return _characterSpecifiers; } } private readonly string _specifier; public CharacterClass(string specifier) { Match m = Matcher.Match(specifier);
- pattern minor 112d agoCode Explorer UIThe third post in the Code Explorer series is about the UI and main VM. This is the XAML, except a few hundred lines of styling: ```
- pattern minor 112d agoCode Explorer View ModelsContinuing the series of Code Explorer posts, here is the collection of view models for the tree nodes: This is the interface for nodes with a declaration. ``` public interface ICodeExplorerDeclarationViewModel { Declaration Declaration { get; } } ``` And here is the abstract view model they are all based on: ``` public abstract class CodeExplorerItemViewModel : ViewModelBase { private List _items = new List(); public List Items { get { return _items; } protected set { _items = value; OnPropertyChanged(); } } public bool IsExpanded { get; set; } public abstract string Name { get; } public abstract string NameWithSignature { get; } public abstract BitmapImage CollapsedIcon { get; } public abstract BitmapImage ExpandedIcon { get; } public abstract CodeExplorerItemViewModel Parent { get; } public abstract QualifiedSelection? QualifiedSelection { get; } public CodeExplorerItemViewModel GetChild(string name) { foreach (var item in _items) { if (item.Name == name) { return item; } var result = item.GetChild(name); if (result != null) { return result; } } return null; } public Declaration GetSelectedDeclaration() { return this is ICodeExplorerDeclarationViewModel ? ((ICodeExplorerDeclarationViewModel)this).Declaration : null; } public void AddChild(CodeExplorerItemViewModel item) { _items.Add(item); } public void ReorderItems(bool sortByName, bool sortByType) { if (sortByType) { Items = sortByName ? Items.OrderBy(o => o, new CompareByType()).ThenBy(t => t, new CompareByName()).ToList() : Items.OrderBy(o => o, new CompareByType()).ThenBy(t => t, new CompareBySelection()).To
- pattern minor 112d agoCode Explorer CommandsRubberduck's Code Explorer was recently redesigned from scratch: Anything from modern features, such as virtual folders (limitation of the VBE--it doesn't support real folders), to ancient features in the original VBE, such as printing. Here are some of the commands I wrote that I'd like to get reviewed: The Indent command. This command calls our Smart Indenter port and works on any node. On member nodes, procedures are indented (nothing happens if the node is a field); for component nodes, the selected component is indented; on folders, all components in the folder are indented; and on project nodes, all components in the project are indented. ``` public class CodeExplorer_IndentCommand : CommandBase { private readonly RubberduckParserState _state; private readonly IIndenter _indenter; private readonly INavigateCommand _navigateCommand; public CodeExplorer_IndentCommand(RubberduckParserState state, IIndenter indenter, INavigateCommand navigateCommand) { _state = state; _indenter = indenter; _navigateCommand = navigateCommand; } public override bool CanExecute(object parameter) { if (parameter is CodeExplorerComponentViewModel) { var node = (CodeExplorerComponentViewModel)parameter; if (node.Declaration.Annotations.Any(a => a.AnnotationType == AnnotationType.NoIndent)) { return false; } } if (parameter is CodeExplorerProjectViewModel) { if (_state.Status != ParserState.Ready) { return false; } var declaration = ((ICodeExplorerDeclarationViewModel)parameter).Declaration; return _state.AllUserDeclarations .Any(c => c.DeclarationType.HasFlag(DeclarationType.Module) && c.Annotations.All(a => a.AnnotationType != AnnotationType.NoIndent) && c.Project == declar
- pattern minor 112d agoEvaluating Parser StateThe "parser state" of a module in rubberduck can be one of several values: ``` //note: ordering of the members is important public enum ParserState { /// /// Parse was requested but hasn't started yet. /// Pending, /// /// Project references are being loaded into parser state. /// LoadingReference, /// /// Code from modified modules is being parsed. /// Parsing, /// /// Parse tree is waiting to be walked for identifier resolution. /// Parsed, /// /// Resolving identifier references. /// Resolving, /// /// Parser state is in sync with the actual code in the VBE. /// Ready, /// /// Parsing could not be completed for one or more modules. /// Error, /// /// Parsing completed, but identifier references could not be resolved for one or more modules. /// ResolverError, } ``` Now, Rubberduck uses each modules' state, and determines what the "overall" state is - and then displays that value in a status bar. The rules are: - If all modules have the same state, overall state is that value. - If any module is in an error state, overall state is that error value. - Overall state can only be "ready" when all modules are "ready". - Overall state can only be "parsed" when all modules are "parsed". - Otherwise return the state of the most advanced non-ready module. The code started simple, got complicated, then too simple, and now looks like this - basically it went back to "too complicated": ``` private static readonly ParserState[] States = Enum.GetValues(typeof(ParserState)).Cast().ToArray(); private ParserState EvaluateParserState() { var moduleStates = _moduleStates.Values.ToList(); var state = States.SingleOrDefault(value => moduleStates.All(ps => ps == value)); if (state != default(ParserState)) { // if all modules are in the same state, we have our result. Debug.WriteLine("ParserState evalua