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

Issue4282 import module folder annotation #4444

Conversation

IvenBach
Copy link
Member

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?

@IvenBach IvenBach added the PR-Status: WIP Pull request is work-in-progress, more commits should be expected. label Oct 16, 2018
var tempHelper = (CodeExplorerItemViewModel)parameter;
var updatedFolder = (parameter is CodeExplorerCustomFolderViewModel) ? tempHelper.Name : tempHelper.GetSelectedDeclaration().CustomFolder;

const string folderAnnotation = "'@Folder";
Copy link
Member

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

Copy link
Contributor

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

Copy link
Contributor

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

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.

Copy link
Contributor

@MDoerner MDoerner left a 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());
Copy link
Contributor

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.

Copy link
Member Author

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)
Copy link
Contributor

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

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.

Copy link
Member Author

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.

Copy link
Contributor

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('"');
Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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";
Copy link
Contributor

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

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";
Copy link
Contributor

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)
Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Contributor

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

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.

Copy link
Contributor

@MDoerner MDoerner left a 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());
Copy link
Contributor

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.

Copy link
Contributor

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

Rubberduck.Core/UI/CodeExplorer/Commands/ImportCommand.cs Outdated Show resolved Hide resolved
Rubberduck.Core/UI/CodeExplorer/Commands/ImportCommand.cs Outdated Show resolved Hide resolved
Rubberduck.Core/UI/CodeExplorer/Commands/ImportCommand.cs Outdated Show resolved Hide resolved
Rubberduck.Core/UI/CodeExplorer/Commands/ImportCommand.cs Outdated Show resolved Hide resolved
@IvenBach
Copy link
Member Author

This is still using VBAParser. It needs to be updated with a more robust option to allow for conditional compilation as @MDoerner has already pointed out. I'll need some help with getting this understood and in working order. One step at a time.

@IvenBach IvenBach force-pushed the Issue4282_Import_module_folder_annotation branch 2 times, most recently from ed5eec9 to b729341 Compare October 31, 2018 01:04
Copied everything over from home to get work computer able to build.
@IvenBach IvenBach force-pushed the Issue4282_Import_module_folder_annotation branch 3 times, most recently from 97dd065 to 556a3ce Compare October 31, 2018 17:32
@bclothier bclothier added the PR-Status: Conflicting PR can't be merged as it stands, conflicts must be resolved by the author. label Dec 29, 2018
@bclothier
Copy link
Contributor

Closing due to age/staleness. If anyone wants to pick it up, feel free to reopen.

@bclothier bclothier closed this Jan 3, 2019
@IvenBach IvenBach deleted the Issue4282_Import_module_folder_annotation branch May 9, 2019 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR-Status: Conflicting PR can't be merged as it stands, conflicts must be resolved by the author. PR-Status: WIP Pull request is work-in-progress, more commits should be expected.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants