New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reorganizing Rewriting #4465
Reorganizing Rewriting #4465
Conversation
The extraction is performed into the new IExecutableModuleRewriter, which extends IModuleRewriter. This change allows to restrict the access to triggering the actual application of the changes. This will be important in the next commits. It was necessary to change some other functionality using the rewriter with dynamic arguments in order to avoid runtime binder exceptions. (It does not see the interfaces that get extended.) The first change was to the (I)ModuleRewriter itself. I added a new overload both for Replace and Remove taking an IParseTree. This is implemented using pattern matching to prefer execution for the ITerminalNode and then falling back to ParserRuleContext, which is always a valid downcast for IParseTree. This allows to avoid using dynamic to prefer the calls with ITerminalNode. The other changes were explicit casts of the second argument of Replace, which only accepts strings anyway and a change from dynamic to patterns matching in a quickfix.
These two are meant to build the foundation for a new way to manage rewriting modules in a consistent fashion. When a feature wants to rewrite a module, it needs to have the IRewritingManager injected. Then it can request a IRewriteSession from it, either for code pane rewrites or attributes rewrites. The sessions manage the rewriting of all checked out rewriters and function as a point to perform pre- and post-rewrite actions in a consistent manner. The manager and sessions are set up such that only one active rewrite session may ever perform a successful rewrite: the manager will invalidate all active rewrite sessions on the first rewrite of an active rewrite session. (To coordinate this a callback gets passed to the freshly created sessions.) The current setup of the StateTokenStreamCache is temporary only; a change to only storing ITokenStreams on the RubberduckParserState is envisioned for the future.
When parsing, all rewrite sessions get invalidated because any change, like removing components, changing referenced projects or their priority or reparsing components may invalidate the basis for a rewrite or make the underlying component inaccessible.
This change is to be reverted later. It allows to decouple the work necessary to change the users of the IModuleRewriter to use the IRewritingManager from the work necessary to constructor inject the IRewritingManager into them. (The latter is a lot of work in the tests.) Here, property injection is used because the construction of the RewritingManager ultimately depends on the RubberduckParserState to be there.
This is a first step to make them use the IRewriteSession model. Passing it is, will allow the QuickFixProvider to pass in the same session for all results when fixing more than one resulte.g. for FixAll.
This change ensures that whenever a fix is used for multiple results, the same IRewriteSession is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice! Mainly questions.
Rubberduck.CodeAnalysis/QuickFixes/RemoveDuplicatedAnnotationQuickFix.cs
Outdated
Show resolved
Hide resolved
Linking #4356 - was going to take a look at that one, but based on the errors it would be better to wait for this PR to merge. |
The tests are still pending; that is another undertaking.
In addition, the tests are streamlined to use methods from a base class in order to reduce code duplication.
This is now handled via IRewriteSessions.
This is only a first step. The rewriters are still stored on the ModuleStates because providing a new one each time a rewriter is requested from the RubberduckParserState breaks the refactoring tests. Ultimately, the rewriters should only be created via the IRewriteSession.
With the new rewriting setup the source of IModuleRewriters changed.
It was only ever needed because of a superfluous part of the execution.
…o them The quickfixes using a refactoring currently cannot pass on the IRewriteSession to the refactoring, which handles its own rewriting separately. Moreover, the AddIdentifierToWhitelistQuickfix does not need it because it is not changing code.
…tial selection Previously, it used a tuple of ICodeModule and Selection, which was leaking SCWs.
This includes a bug fix to the RewritingManager.
The ReorderParametersCommand and its tests had to change, too.
Also contains one correction to a test for the RenameRefactoring
All interaction with rewriters is to go through an IRewriteSession in the future, which is acquired from an IRewritingManager.
All users have been rewired. So, it does not have to be property injected anymore.
# Conflicts: # Rubberduck.CodeAnalysis/QuickFixes/RemoveUnassignedVariableUsageQuickFix.cs # RubberduckTests/QuickFixes/AccessSheetUsingCodeNameQuickFixTests.cs # RubberduckTests/Refactoring/MoveCloserToUsageTests.cs
Contains extraction of a partial interface from the RubberduckParserState (IParseManager) in order to allow mocking the corresponding functionality.
Also makes the IRewriteSession expose the target code kind in order to make the RewritingManager testable.
# Conflicts: # Rubberduck.CodeAnalysis/QuickFixes/RenameDeclarationQuickFix.cs
This change on the IRewriteSession allows the user to see whether the rewrite actually happened successfully.
The Rewrite member is no longer on the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've skipped all the tests. 👍 from me, there's some tech-debt with how refactorings work, but that's out of scope for this PR.
Rubberduck.CodeAnalysis/QuickFixes/RemoveDuplicatedAnnotationQuickFix.cs
Show resolved
Hide resolved
Rubberduck.CodeAnalysis/QuickFixes/RemoveEmptyIfBlockQuickFix.cs
Outdated
Show resolved
Hide resolved
Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceRefactoring.cs
Outdated
Show resolved
Hide resolved
Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceRefactoring.cs
Outdated
Show resolved
Hide resolved
AdjustReferences(_model.TargetDeclaration.References, _model.TargetDeclaration, rewritingSession); | ||
AdjustSignatures(rewritingSession); | ||
|
||
rewritingSession.TryRewrite(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a contract that refactorings have to trigger the sessions's rewrite themselves? If not, I'd prefer they worked like quickfixes do ..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked about the approach to take in this PR in chat. @retailcoder opted for this version. The alternative would have required to change the IRefactoring
interface.
Reparse(); | ||
var rewriteSession = _rewritingManager.CheckOutCodePaneSession(); | ||
Rename(rewriteSession); | ||
rewriteSession.TryRewrite(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit annoyed that each Refactoring implementation needs to remember doing that. Is there a clean way to fix this?
I assume there's some issues with refactorings that use dialogs that makes it hard to correctly unify this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They all require a session to be able to rewrite and that session and the changes need to be applied in the end. Before this PR, it was not much better: every refactoring had to call rewrite on the appropriate rewriters in the end.
Moreover, every refactoring uses a different path through the interface methods to apply the refactoring.
So, without further cleanup or providing the session via method injection (see another comment above) I do not see how to put this in a base class.
Removed a typo and a superfluous comment, changed to property use from backing field in one occasion and adapted the exception message in a switch statement.
This PR is still WIP. However, I wanted to put it out for review to get featback on the setup before doing all the rewiring necessary.
This PR aims to provide more structure to how rewriting is handled in RD. Currently, Any feature wanting to rewrite modules simply gets the appropriate rewirters from the
RubberduckParserState
and then either callsRewrite
on the rewriters directly or callsRewriteAllModules
on theRubberduckParserState
. This is rather unstructured and has some caching problems because everybody gets the same rewriters cached on the state. Moreover, it is completely up to the feature whether a reparse gets initiated afterwards and whether a selection gets recovered.This PR introduces the concept of a
IRewriteSession
, which is an independent unit of rewriting. It hands out individualIModuleRewriters
that no longer have aRewrite
method. To apply the changes,Rewrite
has to be called on theIRewriteSession
. Doing so, causes all checked out rewriters to be applied. Moreover, it forces a reparse. (Here, other behaviour like recovering the selection or recreating member attributes could be added in future PRs.)Individual
IRewriteSessions
can be acquired from theIRewirtingManager
. It holds a list of all active sessions and will invalidate all of them once one of them actually applies its rewrites. Moreover, on each parse, all rewrite sessions will get invalidated.Future steps will include:
IRewriteSession
s. (done)ITokenstream
s on theModuleState
s instead of rewriters. (done)IRewritingManager
wherever it gets acquired from theRubberduckParserState
. (done)RewritingManager
property from theRubberduckParserState
. (done)RewritingManager
and theIRewritesSession
implementation. (done)