-
Notifications
You must be signed in to change notification settings - Fork 302
RubberduckParserState
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 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 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 IdentifierReference
s 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.
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.
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.
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.
rubberduckvba.com
© 2014-2021 Rubberduck project contributors
- Contributing
- Build process
- Version bump
- Architecture Overview
- IoC Container
- Parser State
- The Parsing Process
- How to view parse tree
- UI Design Guidelines
- Strategies for managing COM object lifetime and release
- COM Registration
- Internal Codebase Analysis
- Projects & Workflow
- Adding other Host Applications
- Inspections XML-Doc
-
VBE Events