Skip to content

RubberduckParserState

bclothier edited this page Mar 22, 2018 · 7 revisions

The RubberduckParserState is the central piece of Rubberduck. It stores all the information we extract about the modules currently present in the VBE, including parse trees and declarations of modules, procedures and variables. Moreover, it exposes the current state Rubberduck is in as well as a method to request a run of the parsing process.

The RubberduckParserState is accessed by various features, from inspections to refactorings and toolwindows. It is constructor-injected in singleton scope by Castle Windsor. So any class that needs to work with information about the parsed modules simply needs to have a RubberduckParserState constructor parameter.

The Parser State

The possible states of individual modules and Rubberduck as a whole are the different values of the ParserState enum:

public enum ParserState
{
    /// <summary>
    /// Parse was requested but hasn't started yet.
    /// </summary>
    Pending,
    /// <summary>
    /// Project references are being loaded into parser state.
    /// </summary>
    LoadingReference,
    /// <summary>
    /// Code from modified modules is being parsed.
    /// </summary>
    Parsing,
    /// <summary>
    /// Parse tree is waiting to be walked for identifier resolution.
    /// </summary>
    Parsed,
    /// <summary>
    /// Resolving declarations.
    /// </summary>
    ResolvingDeclarations,
    /// <summary>
    /// Resolved declarations.
    /// </summary>
    ResolvedDeclarations,
    /// <summary>
    /// Resolving identifier references.
    /// </summary>
    ResolvingReferences,
    /// <summary>
    /// Parser state is in sync with the actual code in the VBE.
    /// </summary>
    Ready,
    /// <summary>
    /// Parsing could not be completed for one or more modules.
    /// </summary>
    Error,
    /// <summary>
    /// Parsing completed, but identifier references could not be resolved for one or more modules.
    /// </summary>
    ResolverError,
    /// <summary>
    /// This component doesn't need a state.  Use for built-in declarations.
    /// </summary>
    None,
}

The global state of Rubberduck can both be set directly or be derived from the individual module states. These options are exposed via the methods SetStatusAndFireStateChanged and EvaluateParserState, respectively. At which points which state gets set via which method can be found in the page about the parsing process.

When evaluating the global state of Rubberduck, the result generally is the lowest module state found. The exception are the two error states; these are always prioritized over all other states.

The RubberduckParserState raises events to indicate a change in the state of Rubberduck; code that requires a specific state to work should register the StateChanged and/or ModuleStateChanged event, and determine what to do depending on the current state.

Features that require resolved identifier usages (inspections, refactorings, etc.) should wait for a global Ready state; features that merely enable navigation and/or comments (e.g. code explorer), don't need to wait for the resolver to complete and can wait for a global ResolvedDeclarations state instead.

Declarations

Declarations are the end product of running the parsing process in Rubberduck. They represent all identifiers inside the projects loaded into the VBE. This includes projects, modules, classes, property getters, property letters, property setters, functions, procedures, variables, constants, events, function and procedure parameters and line labels. We also derive declarations for everything within the type libraries of the project references of the projects currently loaded. (Those in the dialog Tools -> References... of the VBE.)

Declarations carry information about their name, what kind of declarations they are, what type they are (for properties, functions, parameters, variables and constants) and where they are located, among others. Declarations also have a References collection that, once identifier resolution is completed, contains all IdentifierReferences pointing to it, i.e. one record for each usage within the code currently loaded.

By inspecting the declarations and their references, we can programmatically determine whether a variable is used or not, what scope they're declared in and how visible they are, and where and how they're used.

When the parser is in a ResolvedDeclarations state, all modules have successfully been parsed, and all parse trees have been successfully walked and all declarations have been identified. However, it is only in the Ready state that the References collection has been filled, the supertypes have been determined and the types have been resolved for non-base types.

Accessing Declarations

To access the declarations, use the DeclarationFinder exposed as a property on the RubberduckParserState.

    public DeclarationFinder DeclarationFinder

The DeclarationFinder exposes a plethora of methods that can be used to find declarations in all kinds of ways, e.g. all user defined declarations of a specific type can be found via the UserDeclarations function.

    public IEnumerable<Declaration> UserDeclarations(DeclarationType type)

If you are looking for a declaration of a specific name, it is usually most efficient to use the MatchName function, which returns all declarations of this name.

    public IEnumerable<Declaration> MatchName(string name)

The different access methods are backed by several specialized dictionaries. This considerably improves the performance when accessing declarations based on citeria.

Should there be no specialized accessor broad enough for the desired application, one can get access all declarations through the three properties AllUserDeclarations, AllBuiltInDeclarations and AllDeclarations.

    public IEnumerable<Declaration> AllUserDeclarations
    public IEnumerable<Declaration> AllBuiltInDeclarations
    public IEnumerable<Declaration> AllDeclarations

Rubberduck creates a vast amount of Declaration objects to represent built-in modules and members of the project references referenced by currently loaded projects. This always includes the VBA standard library, as well as the host applications object model, e.g. the Excel object model. A lot of features don't need to account for these built-in declarations. Because of this, the dictionaries backing the functions and properties of the DeclarationFinder focused on built-in declarations get populated lazily.

Refreshing the Parser State

Whenever a module gets renamed or removed and whenever we programmatically modify a code module, all declarations and usages point to the wrong locations and need to be refreshed. So, we have to request a reparse.

Code that has a RubberduckParserState dependency can trigger a reparse by calling the OnParseRequested method on the parser state:

_state.OnParseRequested();

It is not possible to request a reparse for a specific module. Which modules have to be reparsed and which have to be reresolved gets determined by the ParseCoordinator as part of the parsing process.

The parser processes everything asynchronously, so the IDE/UI should not be affected by the intense processing going on in the background.

Careful!

Requesting a reparse will change the parser state, and all features that have code to execute when parser state changes, will run that code. For this reason, the code that runs on the UI thread upon a change in parser state should be kept to a minimum, to avoid blocking the UI. Consider implementing extensive work on a background thread.

Clone this wiki locally