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
Remove Regex-Replaces/Raw String ops from #2660 part 2 #2733
Conversation
…e it a static class
|
||
namespace Rubberduck.Common | ||
{ | ||
public static class ICodeModuleExtensions |
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.
The I
prefix indicates an interface; I'd rename this one to CodeModuleExtensions
; this is similar to LINQ's Enumerable
static class, which extends the IEnumerable
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.
Will make the change.
var result = ReplaceStringAtIndex(original, token.Text, replacement, token.Column); | ||
module.ReplaceLine(token.Line, result); | ||
} | ||
public static void ReplaceIdentifierReferenceName(this ICodeModule module, IdentifierReference identifierReference, string replacement) |
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.
Nitpick: I like to have an empty line between members of a class - that would be consistent with the rest of the code base.
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.
That said the class looks pretty neat otherwise 👍
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.
Will make the change.
public override bool CanFixInProject { get { return false; } } | ||
|
||
//This function exists solely to support unit testing | ||
public void TESTONLY_FixUsingAutoGeneratedName(string suggestedName = "") |
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 don't like this... a class shouldn't have the knowledge of whether or not it's being executed in a test context. There has to be a better way.
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.
Take a look at the RenameDeclarationQuickFix
and associated tests for an example of how to approach this. The "testing only" part is basically just to suppress the dialog - if you make the dialog a dependency, you can mock it in the tests.
{ | ||
view.NewName = _localCopyVariableName; | ||
view.IdentifierNamesAlreadyDeclared = _variableNamesAccessibleToProcedureContext; | ||
if (!_isQuickFixUnitTest) |
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.
The problem is that there's this AssignedByValParameterQuickFixDialog
dependency that needs to be tamed.
You could constructor-inject an abstract factory here - and Ninject will do all the hard work!
public interface IAssignedByValParameterQuickFixDialogFactory
{
IAssignedByValParameterQuickFixDialog Create(Declaration target, Selection selection, string[] accessibleNames);
}
Just make the dialog implement an interface that's named after it (with the I
prefix) - that interface could look like this:
public interface IAssignedByValParameterQuickFixDialog : IDisposable
{
void ShowDialog();
DialogResult DialogResult { get; }
string NewName { get; }
}
Make the dialog implement that interface (just tacking : IAssignedByValParameterQuickFixDialog
to its definition should be enough, from what I'm seeing)
And then the AssignedByValParameterMakeLocalCopyQuickFix
can take an IAssignedByValParameterQuickFixDialogFactory
constructor parameter and store that abstract factory in a private readonly field:
private readonly IAssignedByValParameterQuickFixDialogFactory _dialogFactory;
Now, instead of doing using (var view = new AssignedByValParameterQuickFixDialogFactory(_target, Selection))
, you can do this:
using (var view = _dialogFactory.Create(_target, Selection, _variableNamesAccessibleToProcedureContext))
And now whoever creates a AssignedByValParameterMakeLocalCopyQuickFix
is responsible for passing the dependencies to the constructor - I think that would be the inspection result class... so for now let's make that inspection result's constructor intake the IAssignedByValParameterQuickFixDialogFactory
dependency through its constructor, and then since the inspection result is new
'd up by the inspection itself, the inspection's constructor for now will have to intake the IAssignedByValParameterQuickFixDialogFactory
dependency through its constructor as well.
That's dirty dependency injection, but it should "just work". We'll revisit the injecting depedencies of dependencies of dependencies issue when the inspections feature is re-architected.
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.
Forgot to mention: the abstract factory doesn't need a concrete implementation - Ninject will deal with that automatically.
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.
Thanks for providing all this detail. I'll look at the examples and get rid of the dependency (I was a little surprised this topic didn't come up in the very first PR!).
var firstBlockLineNumber = blocks.FirstOrDefault().Start.Line; | ||
|
||
var module = Selection.QualifiedName.Component.CodeModule; | ||
module.InsertLines(firstBlockLineNumber++, BuildLocalCopyDeclaration()); |
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.
why the ++ here? it might be clearer to extract that into a separate statement.
Alternatively you could join the two declarations and insert them together
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 can extract it. Originally, I had a CodeModuleExtension function InsertLinesAfter( int lineNumber, string[] lines) but thought it might be overkill for inserting just two lines. Would putting a function like this in the CodeModule extension class make sense in general?
@@ -647,7 +647,7 @@ If the parameter can be null, ignore this inspection result; passing a null valu | |||
<data name="ApplicationWorksheetFunctionQuickFix" xml:space="preserve"> | |||
<value>Use Application.WorksheetFunction explicitly.</value> | |||
</data> | |||
<data name="AssignedByValParameterQuickFix" xml:space="preserve"> | |||
<data name="AssignedByValParameterMakeLocalCopyQuickFix" xml:space="preserve"> |
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.
Note: needs to also be adjusted in the other languages
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.
There were not pre-existing entries in fr.resx and de.resx to replace for 'AssignedByValParameterQuickFix'. So, I'm planning to add the entry with the english description unless someone can provide translations...Or, if it is better to leave them missing than entered in english. Thanks.
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.
If they don't exist yet, you may as well leave them missing ... Because then the translators for the respective languages (me and retailcoder) need to get to those either way. On that note: @retailcoder we should probably do a i18n check for the next release either way ...
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.
👍
…duck-vba/next' into next
…rMakeLocalCopyQuickFix
This version removes the QuickFix's dependency on the dialog class. Used two implementations of a factory interface to accomplish the result. Could not get figure out how to get there with Mock so created a simple Mock class of my own. At the bottom of AssignedByValParameterMakeLocalCopyQuickFixTests.cs you'll find the test-related factory implementation and Mocked class. |
[TestClass] | ||
public class AssignedByValParameterMakeLocalCopyQuickFixTests | ||
public class AssignedByValParameterMakeLocalCopyQuickFixTests : IAssignedByValParameterQuickFixDialogFactory |
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.
The test class implements the factory interface... why?
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.
The QuickFix calls "Create(...)" on the factory interface passed into the constructor. During unit testing, the Testing class passes in a factory interface that points to itself so that it can construct a and return an interface to the MockDialog in order to support the tests.
|
||
namespace Rubberduck.UI.Refactorings | ||
{ | ||
public interface IAssignedByValParameterQuickFixDialog : IDialogView |
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.
👍 extends IDialogView
@@ -25,9 +26,10 @@ public override IEnumerable<QuickFixBase> QuickFixes | |||
{ | |||
get | |||
{ | |||
IAssignedByValParameterQuickFixDialogFactory factory = new AssignedByValParameterQuickFixDialogFactory(); |
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.
The factory is a dependency, it should be constructor-injected.
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.
So, do you mean moving this (or a similar) line of code to the constructor of the Inspection class? I'm OK with moving it....I'm wondering if the constructor of the inspection should carry the factory interface dependency for a dialog component of one of the three quickfixes - it seems a little 'high up' - but it's likely I'm not understanding what you are advocating. Could you comment a bit more? Thanks.
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 went back and reviewed your earlier comments on this topic...I can see that they are consistent with the above comment - I think I just lost track of where to bring in the dependency once I started coding. Soooo...I'll have another update coming soon.
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.
This latest update to the PR (2/25) is much closer (I'm hoping ) to what you expected to see. The couple lines of code I added to RubberDuckModule.cs look out of place to me, so I suspect there is a better way. As I'm sure you've already figured out, using Ninject is new to me...just another one of many things here at RD!.
…vba/next' into next
…vba/next' into next
@@ -425,6 +429,10 @@ private IEnumerable<ICommandMenuItem> GetRubberduckCommandBarItems() | |||
|
|||
private IEnumerable<IMenuItem> GetRubberduckMenuItems() | |||
{ | |||
//This bind needs to occur before the the new array is built by the function calls |
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.
That's because the factory interface naming convention is kicking in and binding to an automatic factory implementation; if you want to override that binding you can use Rebind
- please keep GetRubberduckMenuItems
dedicated to returning the RD menu items.
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.
Thanks for the feedback on this particular change. Using Rebind did not succeed...I'm thinking it is because the Inspection objects are constructed just once (when the menu items are created) and there is never another opportunity to inject the redefined dependency with Rebind(). (True?) I've moved the Bind definition to a sequentially (and contextually) correct place in the body of the "Load()' function.
| ||
namespace Rubberduck.UI.Refactorings | ||
{ | ||
public class AssignedByValParameterQuickFixMockDialogFactory : IAssignedByValParameterQuickFixDialogFactory |
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.
We don't need to make actual mock implementations to inject in the tests - we use a mocking framework for that (Moq).
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'll figure out how to use the Moq framework.
@@ -49,7 +49,7 @@ public override void Fix() | |||
|
|||
private void RequestLocalCopyVariableName() | |||
{ | |||
using( var view = _factory.Create(_target.IdentifierName, _target.DeclarationType.ToString())) | |||
using( var view = _dialogFactory.Create(_target.IdentifierName, _target.DeclarationType.ToString())) |
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.
isn't this blowing up with a null reference exception in the tests when you pass a null
factory to the inspection? You'll want to give it a Mock<IAssignedByValParameterQuickFixDialogFactory>
object, something like this:
var mock = new Mock<IAssignedByValParameterQuickFixDialogFactory>();
mock.SetupGet(m => m.NewName).Returns(() => WhateverTheTestExpectsTheNewNameToBe);
mock.SetupGet(m => m.IdentifierNamesAlreadyDeclared).Returns(() => WhateverTheTestNeedsToWorkWith);
mock.SetupGet(m => m.IsCancelled).Returns(() => false); // or true, to test cancellation behavior
var inspection = new AssignedByValParameterInspection(mockParser.State, mock.Object);
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.
All the tests pass free of any exceptions - The factory reference is not de-referenced unless the test invokes "Fix()" on the MakeLocalCopyQuickFix. Once I sort out to use the Moq framework for the factory - I'll pass it to the constructor.
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.
@BZngr ah, sorry I missed that! don't bother setting up a mock for a test that doesn't need it ;-)
I guess a mock will be useful for a test that tests the quickfix itself.
I don't have push access to your fork so I can't fix the merge conflict from here, but it should be a pretty simple one to fix 😃 Let me know when you're ready to merge the PR! |
Yes - the conflicts are straight forward - locally I've already resolved them. Yet to add the code to use the Moq framework. Hoping to have time to do it later today. Thanks. |
…bberduck-vba/next' into next
All comments addressed - I'm thinking it's finally ready to go. |
Currently failing tests on AppVeyor:
I don't know in which way that was caused by this PR ... |
When I look at the content of these tests, any relationship to this PR is not obvious to me. The tests appear to be independent of code that was modified. I also ran these tests in loops. The results vary slightly with each run of the loops. However, the results below are typical: EnclosingProjectComesBeforeOtherProceduralModule
EnclosingProjectComesBeforeOtherModuleInEnclosingProject
GivenLocalDeclarationAsQualifiedClassName_ResolvesFIrstPartToProject
|
@BZngr Do not worry about the failed tests. These are three of our beloved six tests that fail an pass randomly for about two months now. We still have not figured out what is causing it but it must be some concurrency issue in the tests. |
FWIW - There is a simple way to mask the concurrency issue yet allow the tests to run and detect 'hard' failures. Just give the Parser several tries to get the right answer prior to evaluating the results within the Assert. The example below is for 'EnclosingProjectComesBeforeOtherProceduralModule':
This approach works for all three tests. The loop is different for GivenLocalDeclarationAsQualifiedClassName_ResolvesFIrstPartToProject, but the principle is the same. |
Looking for some input. The PR as it stands is an improvement over the version of the QuickFix currently in 'next'. That said, there is still some work to do to expand the assistance to the user in choosing a new name. That is, to include module scope and global scope variable name checks. I am currently working on this. So...my question is regarding the preference at this point - and I am good with either. Do you want to hold off on this PR - and I'll continue to push improvements as I have them ready? Or, proceed as it is to get the current improvements and identify the additional checks as a bug. Just wanted to make sure it was clear what is missing (as far as I know) from the PR as it currently exits. Thanks. |
@BZngr sorry for the delay! no worries, I just hadn't had a chance to get to it yet. On it! |
Follow up to rejected PR to #2714. Updates AssignedByValParameterQuickFixes to use ParseTree results. Incorporates comments from PR #2174. Adds an ICodeModuleExtensions class per PR #2714 comments.