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
Refactor parse tree inspections #5404
Refactor parse tree inspections #5404
Conversation
Also adds a base for inspection listeners.
The DeclarationFinderProvider remains protected for now.
Require an IDeclarationFinderProvider instead of a RubberduckParserState.
Also adds the missing configurationd for the references libraries in tests.
Also removes the use of a CodeModule.
Also refactors the class a bit and fixes several possible multiple enumeration issues.
It specifies the minimal number of arguments for the annotation.
Also adds an overload of DeclarationFinder.FindAnnotations providing all annotations in a module. IllegalAnnotationInspection now uses this in favor of RubberduckParserState.GetAnnotations, which allows to remove the dependency on the state.
Codecov Report
@@ Coverage Diff @@
## next #5404 +/- ##
=========================================
+ Coverage 60.91% 61.51% +0.6%
=========================================
Files 1175 1195 +20
Lines 40332 40570 +238
Branches 8536 8644 +108
=========================================
+ Hits 24568 24955 +387
+ Misses 13824 13788 -36
+ Partials 1940 1827 -113
|
Also add allowed number of arguments to IAnnotation interface.
Also removes the special testing interface from ParseTreeValueVisitor.
The inspector swallows exceptions, which we really do not want in tests.
It now correctly depends on the module and passes along the found results instead of aggregating them in an instance variable.
This allows to remove the corresponding state from the ParseTreeValueVisitor.
…stener in UnreachableCaseInspection
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.
Great work as always; I skimmed over minor changes but have the following questions.
public virtual CodeKind TargetKindOfCode => CodeKind.CodePaneCode; | ||
} | ||
|
||
public class InspectionListenerBase : VBAParserBaseListener, IInspectionListener |
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 think we might want to consider sticking to the rule of one type per one file. Maybe even consider having all bases in a folder for easy grouping.
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.
Except for generic abstract classes inherited from a non-generic abstract class? 😉
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.
For parse tree inspections, we have always put the listeners into the same file.
I think, in many cases, we can actually make them private classes.
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 do agree with inlining the listeners in the implementation since most of the time they will be specific to only one inspection so it makes sense to make it a nested & private class. We do have to consider the discoverability aspect for the abstract classes, however. If I'm new to the code, I cannot see that there are both ParseTreeInspectionBase
and InspectionListenerBase
by viewing the filesystem alone. I'd have to use something like Find Implementation on the IInspectionListener
to see that I need to create my listener based on the InspectionListenerBase
. I am not sure off the cuff if the Find Implementation won't give cluttered results with all other concrete implementations.
string.Format(InspectionResults.BooleanAssignedInIfElseInspection, | ||
(((VBAParser.IfStmtContext)result.Context).block().GetDescendent<VBAParser.LetStmtContext>()).lExpression().GetText().Trim()), | ||
result)); | ||
var literalText = ((VBAParser.IfStmtContext) context.Context) |
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 way it is, it requires upcasting from general ParserContext
to a specific context. Given that we had the specific context available to us when listening, are we better off constructing the strings and saving it in the contexts dictionary (as a tuple, perhaps), thus simplifying the ResultDescription
and cut out potential runtime errors resulting from incorrect upcasting in all and future implementations of the parse tree inspections?
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 not a crazy idea at all. Actually there's something odd about losing all benefits of generics here... it's almost like.. like it wants to be string ResultDescription<TContext>(QualifiedContext<TContext> context) where TContext : ParserRuleContext
? Or that's.. probably a rabbit hole in generics wonderland...
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 already have an idea how to wire up a generic version. However, this will need rework on all parse tree inspections and on the interface and its users as well.
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.
Since the latest push, the parse tree inspection base classes and the listeners are generic with respect to the context type.
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 think the latest changes is a vast improvement, nicely done! The only nitpick I have is that a select number of inspections use the general ParserRuleContext
as the TContext
due to the fact that they work with more than one contexts. Ideally, it would be possible to statically enumerate valid contexts they can have but I don't see a solution for that short of adding marker interfaces to various parser rule contexts. For this PR, I'm happy with the direction you have taken.
@@ -45,18 +43,16 @@ namespace Rubberduck.Inspections.Concrete | |||
[Experimental(nameof(ExperimentalNames.EmptyBlockInspections))] |
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.
Unrelated but is this still necessary? The same question applies to other empty whatever inspections.
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 remove the experimental attributes, if you want.
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 think we should change the severity to Hint, though.
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.
@MDoerner agreed, Hint
works. Were the attributes removed?
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.
Should I remove them?
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.
Yeah, now's a good time :)
@@ -13,15 +13,15 @@ public abstract class DeclarationInspectionBaseBase : InspectionBase | |||
protected readonly DeclarationType[] RelevantDeclarationTypes; | |||
protected readonly DeclarationType[] ExcludeDeclarationTypes; | |||
|
|||
protected DeclarationInspectionBaseBase(RubberduckParserState state, params DeclarationType[] relevantDeclarationTypes) | |||
: base(state) | |||
protected DeclarationInspectionBaseBase(IDeclarationFinderProvider declarationFinderProvider, params DeclarationType[] relevantDeclarationTypes) |
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 is why I'm less certain than before about the one class: one file rule/guideline, @bclothier: I'm thinking the only reason this class needs a silly name here is because of one class: one file, because DeclarationInspectionBase<T>
wants to go into a DeclarationInspectionBase.cs
file.
I'm pretty sure we could have DeclarationInspectionBase
and DeclarationInspectionBase<T>
in the same DeclarationInspectionBase.cs
file, and avoid needing to name our abstract classes ThingBaseBase
😜
@MDoerner what do you think?
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 admit that I am not consistent on that guideline myself. But FWIW, in this particular case, I had files RefactoringDialogBase
and RefactoringDialogBaseGeneric
. Note that in the generic file, the class is just RefactoringDialogBase<>
; no need for BaseBase
.
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.
Ah, but then you sacrifice the consistency of classname.cs
! #NamingIsHard
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.
Also, in this particular example, this isn't generic. It's basically a InspectionBase
implementing a particular implementation for declaration inspections. In which case, I'm not sure it should be DeclarationInspectionBaseBase
; simply just DeclarationInspectionBase
is sufficient. The fact that it's based on another abstract class (InspectionBase
) is not really material and shouldn't factor into the naming, IMO.
Yes, agreed on #NamingIsHard.
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 have to admit that I don't really like the name myself. It is called that way because it is the base class for the other declaration inspection base classes. Apart from the ones you already mentioned there are also the two multi result base classes.
That it is based on InspectionBase
is irrelevant for the name.
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.
Btw, the generic and non-generic versions of the base classes are already in the same file for each kind.
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.
Should I do something about this class or not? Options are
- Rename
DeclarationInspectionBase
toDeclaratiinInspectionSingleResultBase
andDeclarationInspectionBaseBase
toDeclarationInspectionBase
. That removes theBaseBase
but opens room for confusion since this class should exclusively be implemented by base classes and not concrete inspection. - Inline the class into all three implementation inspection base classes.
- Leave it as-is.
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.
Your comment makes me realize that I had the BaseBase
backward; I interpreted it as a Base
class based on another Base
class but you're meaning a Base
class that needs to be implemented by other Base
classes. I had to look at the DeclarationInspectionBase
to see that it inherits DeclarationInspectionBaseBase
. Obviously that ambiguity is not what we want.
The only suggestion I can think of is PartialBase
, in similar fashion to the partial
class to suggest that it's not a complete definition, but I am not sure that is any improvement. At very least, there should be a xml docs on the BaseBase
explaining that it is meant to be implemented by other base classes and not directly.
Rubberduck.CodeAnalysis/Inspections/Concrete/BooleanAssignedInIfElseInspection.cs
Show resolved
Hide resolved
Providing the finder as an argument of the templated method could eliminate the need to expose it in some |
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 amount of tech debt being paid here is phenomenal - is the draft/WIP status still valid? Let's merge this!
The draft status is still valid. Based on comments, I will make both the listener base and the parse tree inspection base generic and wire them up correctly. Moreover, I am not done with |
…ead of the provider
…ntations as method parameter Before, the provider was protected and that was used, getting a new one in all base classes for the different kinds of inspections.
Also turns the ParseTreeValueVisitor in UnreachableCaseInspection into a constructor injected field.
Also turns the inspector in UnreachableCaseInspection into a constructor injected field.
I do not really like that |
@@ -7,7 +7,7 @@ | |||
|
|||
namespace Rubberduck.Inspections.Concrete | |||
{ | |||
public class EmptyBlockInspectionListenerBase<TContext> : InspectionListenerBase<TContext> | |||
internal class EmptyBlockInspectionListenerBase<TContext> : InspectionListenerBase<TContext> |
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.
Would it be cleaner to have an abstract EmptyBlockInspectionBase<TContext>
with a protected EmptyBlockInspectionListenerBase<TContext> ContextListener { get; }
? That way the listener becomes private to the EmptyBlockInspectionBase
.
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 Base
suffix says it already, but having this an abstract
class would make it compiler-enforced that it's not intended to be instantiated directly, too.
Also fixes wrong InternalsVisibleTo attributes.
Also sets the severity to Hint.
fb9b8bb
to
b527bb3
Compare
Also adds analyzers for the type attribute of the module element.
Also makes this mandatory from now on.
PSA: once this PR gets merged, everybody might have to rebuild the RubberduckMeta solution because this PR fixes the inspection XML-doc analyzer. Otherwise, you might get false positives for missing name tags. |
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 know the PR was merged already but few minor nitpicks maybe to address in future PRs.
<value>Duplicate value in 'name' attribute</value> | ||
</data> | ||
<data name="DuplicateNameAttributeDescription" xml:space="preserve"> | ||
<value>The 'name' attribute is used to uniquely identify an element. Consequently, it has to be unique.</value> |
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.
<value>The 'name' attribute is used to uniquely identify an element. Consequently, it has to be unique.</value> | |
<value>The 'name' attribute must uniquely identify an element.</value> |
/// End Sub | ||
/// ]]> | ||
/// </example> | ||
[RequiredHost(""NotExcel"")] |
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 have to point out that the way it is, there's coupling between the xml-docs and the attribute. It would be better for the markup generator to actually read the attributes instead of expecting a hostApp
(or any elements), which means we only have one source of truth and no need to keep thing synchronized.
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 believe MSBuild is the markup generator here... it reads XML-docs though, not attributes. That coupling is hopelessly necessary, unfortunately.
<value>Invalid value for 'type' attribute.</value> | ||
</data> | ||
<data name="InvalidTypeAttributeDescription" xml:space="preserve"> | ||
<value>The values of the 'type' attribute are restricted to the value 'Standard Module', 'Class Module', 'Document' and 'User Form'.</value> |
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: Isn't UserForm
more typical?
Closes #2719
This PR introduces a base class for the
IInspectionListener
implementations and extends the common code in theParseTreeInspectionBase
.In addition, the
InspectionBase
gets modified in two ways. First, a new method is introduced to run inspections separately per module. Second, theState
property gets removed, allowing the inspections to depend only onIDeclarationFinderProvider
instead.Furthermore, the three inspections
ImplicitByRefModifierInspection
,RedundantByRefModifierInspection
andProcedureCanBeWrittenAsFunctionInspection
get turned into declaration inspections, since this very much simplifies them.Since a new member
RequiredArguments
is added toIAnnotation
, theMissingAnnotationArgumentInspection
can be made more extensible and simple. It also only looks at annotations provided by the declaration finder instead of parse trees now.There are also a few more small fixes and refactorings.
This PR is still WIP, since I have to clean up some FIXMEs in
UnreacahbleCaseInspection
.Still, I wanted to get this PR out to get feedback and to pose the question whether we should always provide a declaration finder as argument to the abstract function
DoGetInspectionResults
inInspectionBase
since most inspections need one and get it immediately in that method—with the exception of the parse tree inspections—and since, moreover, the enclosing methodGetInspectionResults
uses one anyway to determine whether results are ignored.