Skip to content
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

Merged
merged 34 commits into from Nov 14, 2018
Merged

Conversation

MDoerner
Copy link
Contributor

@MDoerner MDoerner commented Oct 27, 2018

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 calls Rewrite on the rewriters directly or calls RewriteAllModules on the RubberduckParserState. 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 individual IModuleRewriters that no longer have a Rewrite method. To apply the changes, Rewrite has to be called on the IRewriteSession. 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 the IRewirtingManager. 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:

  1. Wire up all users of the rewriters to use appropriate IRewriteSessions. (done)
  2. Store ITokenstreams on the ModuleStates instead of rewriters. (done)
  3. Constructor inject the IRewritingManager wherever it gets acquired from the RubberduckParserState. (done)
  4. Remove the RewritingManager property from the RubberduckParserState. (done)
  5. Write unit tests for the RewritingManager and the IRewritesSession implementation. (done)

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.
@MDoerner MDoerner added PR-Status: WIP Pull request is work-in-progress, more commits should be expected. PR-Status: Review Requested No more commits, PR is ready for the eyes that need to see it. labels Oct 27, 2018
Copy link
Contributor

@bclothier bclothier left a 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.

@comintern
Copy link
Contributor

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.
@MDoerner MDoerner added the PR-Status: Conflicting PR can't be merged as it stands, conflicts must be resolved by the author. label Nov 1, 2018
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
@MDoerner MDoerner removed the PR-Status: Conflicting PR can't be merged as it stands, conflicts must be resolved by the author. label Nov 3, 2018
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
@MDoerner MDoerner removed the PR-Status: WIP Pull request is work-in-progress, more commits should be expected. label Nov 6, 2018
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.
Copy link
Member

@Vogel612 Vogel612 left a 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.Parsing/Rewriter/RewriteSessionBase.cs Outdated Show resolved Hide resolved
AdjustReferences(_model.TargetDeclaration.References, _model.TargetDeclaration, rewritingSession);
AdjustSignatures(rewritingSession);

rewritingSession.TryRewrite();
Copy link
Member

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 ..

Copy link
Contributor Author

@MDoerner MDoerner Nov 14, 2018

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();
Copy link
Member

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...

Copy link
Contributor Author

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.
@Vogel612 Vogel612 merged commit bf75416 into rubberduck-vba:next Nov 14, 2018
@MDoerner MDoerner deleted the ReorganizingRewriting2 branch December 5, 2018 21:13
@MDoerner MDoerner removed the PR-Status: Review Requested No more commits, PR is ready for the eyes that need to see it. label Feb 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants