-
Notifications
You must be signed in to change notification settings - Fork 309
RubberduckParserState
Parser state is accessed by various features, from inspections to refactorings and toolwindows: it's constructor-injected in singleton scope by Ninject, so any class that needs to work with parser state simply needs to have a RubberduckParserState
constructor parameter.
The parser state raises events to indicate a change in state; code that requires a specific parser state to work should register the StateChanged
and/or ModuleStateChanged
event, and determine what to do depending on current state.
Any given module is always in one of the following states:
public enum ParserState
{
/// <summary>
/// Parse was requested but hasn't started yet.
/// </summary>
Pending,
/// <summary>
/// Parser state is in sync with the actual code in the VBE.
/// </summary>
Ready,
/// <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 identifier references.
/// </summary>
Resolving,
/// <summary>
/// Parsing could not be completed for one or more modules.
/// </summary>
Error
}
The RubberduckParserState
itself also uses this enum to indicate "global state": when all modules are in the same state, the parser state itself is in that state. When any module is in Parsing
, Resolving
or Error
state, then the parser state itself is in that 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 Parsed
state instead.
When the parser is in a Parsed
state, all modules have successfully parsed, and all parse trees have been successfully walked and all declarations have been identified.
Use the AllDeclarations
property to return all Declaration
objects.
/// <summary>
/// Gets a copy of the collected declarations, including the built-in ones.
/// </summary>
public IEnumerable<Declaration> AllDeclarations { get { return _declarations.Keys.ToList(); } }
Rubberduck creates a vast amount of Declaration
objects to represent built-in modules and members of the VBA standard library, as well as some from host application object models, such as the Excel object model. A lot of features don't need to account for these built-in declarations.
Use the AllUserDeclarations
property to retun all Declaration
objects that only involve user code.
/// <summary>
/// Gets a copy of the collected declarations, excluding the built-in ones.
/// </summary>
public IEnumerable<Declaration> AllUserDeclarations { get { return _declarations.Keys.Where(e => !e.IsBuiltIn).ToList(); } }
Shortly put: any identifier that can be referred to in VBA code. In other words, it's a project, it's a module, a class, a property getter, a function, a variable, a constant, an event, its parameters, line labels, ..literally everything.
Declarations have a
References
collection that, once identifier resolution is completed, contains allIdentifierReference
objects that point to it.
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.
As soon as we programmatically modify a code module, all declarations and usages point to the wrong locations and need to be refreshed. While we only need to re-parse the modified module, we cannot be sure that identifier references were added or removed, that belong to declarations that aren't in the module we modified - for example if we have a DoSomething
method in Class1
and we modified Module1
to add a call to Class1.DoSomething
, the declaration for DoSomething
needs its references updated. Hence, even when we parse a single module, the resolver needs to re-process every single parse tree.
Code that has a RubberduckParserState
dependency can trigger a global re-parse by calling the OnParseRequested
method on the parser state:
_state.OnParseRequested();
To trigger the re-parsing of a specific component, pass the modified VBComponent
as a parameter:
_state.OnParseRequested(modifiedModule);
The parser processes everything asynchronously, so the IDE/UI should not be affected by the intense processing going on in the background.
Requesting a re-parse 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-2025 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