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
Issue4282 import module folder annotation #4444
Issue4282 import module folder annotation #4444
Conversation
var tempHelper = (CodeExplorerItemViewModel)parameter; | ||
var updatedFolder = (parameter is CodeExplorerCustomFolderViewModel) ? tempHelper.Name : tempHelper.GetSelectedDeclaration().CustomFolder; | ||
|
||
const string folderAnnotation = "'@Folder"; |
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.
per VBAParserAnnotationFactory
, this should probably be AnnotationType.Folder.ToString()
(and thus not const
).
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 search string means that you are unable to find folder annotations not being the first annotation in an annotation list, e.g. '@Interface @Folder("Interfaces")
.
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.
Moreover, since the @ is contained in the search string, you will always get the annotationList
context, not the individual annotationContext
.
@@ -98,6 +145,31 @@ protected override void OnExecute(object parameter) | |||
} | |||
} | |||
|
|||
private string FolderNameFromFolderAnnotation(IParseTree parseTree, string folderAnnotation) | |||
{ | |||
var searchAnnotation = parseTree.GetText(); |
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 not get the VBAParser.AnnotationContext
node? AnnotationParametersFromContext(VBAParser.AnnotationContext context)
(currently private, but also static
) seems like it could be useful here.
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.
You are doing well already, but you are not yet using the full work the parser has already done for you. You could avoid all the text search based problems from my comments by looking for contexts of type VBAParser.AnnotationContext
only and using the annotationArg child of the first annotationArgList child to identify the folder.
There is one more nasty thing. As it stands, the code will update folder annotations that are not recognized, instead of adding a recognized one. Module annotations are only legal in the declaration section of the module (easy to handle) outside the member annotation sections of member variables (a bit harder to determine). For a first iteration, it might be acceptable though to not consider the member annotation sections.
|
||
string updatedModuleText; | ||
|
||
var result = VBACodeStringParser.Parse(sourceText, t => t.startRule()); |
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 will probably fail if there are precompiler directives in the module. Consider injecting an IStringParser
into the command instead.
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.
From what I on understood your I tried the code below and would only end up with Annotation
as the result of annotationArg.parseTree.GetText()
. What have I misunderstood?
var result = VBACodeStringParser.Parse(sourceText, t => t.startRule());
var antnList = VBACodeStringParser.Parse(result.parseTree.GetText(), x => x.annotationList());
var argListChild = VBACodeStringParser.Parse(antnList.parseTree.GetChild(0).GetText(), x => x.annotationArgList());
var annotationArg = VBACodeStringParser.Parse(argListChild.parseTree.GetChild(0).GetText(), x => x.annotationArg());
return searchAnnotation.Substring(startIndex, length); | ||
} | ||
|
||
private IParseTree TreeContainingFolderAnnotation(IParseTree containingTree, string folderAnnotation) |
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 do a text search? You could either use the already existing AnnotationListener
to get all annotation contexts and search in them for the one of type FolderAnnotation
or build a custom visitor to return the folder annotation context, which is a faster option, I particular since you never have to enter the module body. (Folder annotations are only legal in the declaration section.)
This has the added benefit that you do not find things looking like folder annotations further in inside comments and that you get the actual AnnotationContext
, which has a child for the parameter, which you just have to replace.
using (var components = project.VBComponents) | ||
var extension = filename.Split('.').Last(); | ||
|
||
var sourceText = string.Join(Environment.NewLine, File.ReadAllLines(filename)); |
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.
Could you either hide the file interaction behind an IFileHandler
interface or extract the folder logic into another class. Otherwise, actually testing it is rather painful.
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 IFileHandler
part of Rubberduck or .NET? I couldn't find it in either.
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 is no such interface yet. I was making up a name for some interface hiding away the file interaction. You would either have to find one already existing in .Net or define your own.
private string FolderNameFromFolderAnnotation(IParseTree parseTree, string folderAnnotation) | ||
{ | ||
var searchAnnotation = parseTree.GetText(); | ||
var folderNameEnclosedInQuotes = searchAnnotation.Contains('"'); |
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.
Can't folder names themselves contain double quotes?
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.
What if there is another annotation with a string parameter in the same annotation list?
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 tested with an annotation of '@Folder("Folder"Foo"Bar")
and it wasn't placed within a separate folder. It looks like double quotes aren't allowed.
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 find that actually a bit surprising, but can confirm it. The other point still remains valid, though. An example would be @Description "foo" @Folder(bar)
.
var tempHelper = (CodeExplorerItemViewModel)parameter; | ||
var updatedFolder = (parameter is CodeExplorerCustomFolderViewModel) ? tempHelper.Name : tempHelper.GetSelectedDeclaration().CustomFolder; | ||
|
||
const string folderAnnotation = "'@Folder"; |
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 search string means that you are unable to find folder annotations not being the first annotation in an annotation list, e.g. '@Interface @Folder("Interfaces")
.
var searchAnnotation = parseTree.GetText(); | ||
var folderNameEnclosedInQuotes = searchAnnotation.Contains('"'); | ||
var enclosingCharacter = folderNameEnclosedInQuotes ? '"' : ')'; | ||
var startIndex = searchAnnotation.IndexOf(folderAnnotation) + 1 + folderAnnotation.Length + (folderNameEnclosedInQuotes ? 1 : 0); |
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 will not work if the folder annotation is preceded by another annotation taking parameters in the same annotation list.
var tempHelper = (CodeExplorerItemViewModel)parameter; | ||
var updatedFolder = (parameter is CodeExplorerCustomFolderViewModel) ? tempHelper.Name : tempHelper.GetSelectedDeclaration().CustomFolder; | ||
|
||
const string folderAnnotation = "'@Folder"; |
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.
Moreover, since the @ is contained in the search string, you will always get the annotationList
context, not the individual annotationContext
.
@@ -86,9 +89,53 @@ protected override void OnExecute(object parameter) | |||
|
|||
foreach (var filename in _openFileDialog.FileNames) |
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 not new in this PR, but while you are in here, could you please wrap this loop in a suspension action? As it currently stands, each module import will start a separate parse, potentially invalidating the project reference. (You can find an example in the TestEngine
.)
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.
Suspension action? I didn't see it in TestEngine.cs. This is something I'm not familiar with.
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.
Hou can request go run something in a suspended parser state on the RubberDuckParserState
. @bclothier introduced this a while ago and the test engine is using it to prevent a reparse while running tests.
It is not ghost important to add this right now. It could be added in aother PR.
{ | ||
components.Import(filename); | ||
updatedModuleText = $"{folderAnnotation}({updatedFolder}){Environment.NewLine}" + result.rewriter.GetText(); |
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 might not be an appropriate position. The VBE will probably expect its wall of attributes to come first.
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.
Generally, when using the parser on code, you do not call the method of parser corresponding to what part of the code you want on the end; you use the method for the rule matching the entire code you feed to the parser. (In your case, you have en entire module and, thus, should use startRule
.) Otherwise, it will just fail.
To get a specific part, you generate the full parse tree and than inspect the tree to get the specific bit. To do this, you can define listener, attach it to a walker and walk the tree; you can define a visitor returning the information you want; you can go down the tree by hand, possibly aided by one of our extension methods.
Moreover, I would like to reiterate that the parser used by the VbaCodeStringParser
is not suitable for arbitrary modules because it does not use a preprocessor. It will fail if there are precompiler directives in the module. Inject an appropriate IStringParser
instead and use that to parse the module text.
|
||
string updatedModuleText; | ||
|
||
var result = VBACodeStringParser.Parse(sourceText, t => t.moduleDeclarations()); |
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 will generally fail because you are looking at an entire module, not only a module declaration section.
Moreover, as I stated before, to parse an entire module, inject an IStringParser
. The static VbaCodeStringParser
cannot handle precompiler directives.
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 looks a lot better already.
However, there are still some safety issues for uncommon code..
This is still using |
ed5eec9
to
b729341
Compare
Copied everything over from home to get work computer able to build.
97dd065
to
556a3ce
Compare
…nto Issue4282_Import_module_folder_annotation
Closing due to age/staleness. If anyone wants to pick it up, feel free to reopen. |
Close #4282. I've tested with
.bas
and.cls
modules and had them import successfully, forms I've not dealt with.I plan to encapsulate whatever crossover logic there is for #3332. Are there any glaring issues with what I have so far?