Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upUseConsistentWhitespace - Create option to ignore assignment operator inside hash table #1566
Conversation
… inside hash table
microsoft-cla
bot
commented
Aug 7, 2020
•
| Enable = $true | ||
| CheckInnerBrace = $true | ||
| CheckOpenBrace = $true | ||
| CheckOpenParen = $true | ||
| CheckOperator = $true | ||
| CheckPipe = $true | ||
| CheckPipeForRedundantWhitespace = $false | ||
| CheckSeparator = $true | ||
| CheckParameter = $false | ||
| IgnoreAssignmentOperatorInsideHashTable = $true |
This comment has been minimized.
This comment has been minimized.
| // exclude assignment operator inside of multi-line hash tables if requested | ||
| if (IgnoreAssignmentOperatorInsideHashTable && tokenNode.Value.Kind == TokenKind.Equals) | ||
| { | ||
| var enclosingHashTables = tokenOperations.Ast.FindAll(a => a.Extent.StartOffset <= tokenNode.Value.Extent.StartOffset && a.Extent.EndOffset >= tokenNode.Value.Extent.EndOffset && a is HashtableAst, true); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| { | ||
| Ast innermostEnclosingHashTable = enclosingHashTables.First(); | ||
| int smallestSizeSoFar = int.MaxValue; | ||
| foreach (var hashTable in enclosingHashTables){ |
This comment has been minimized.
This comment has been minimized.
| innermostEnclosingHashTable = hashTable; | ||
| smallestSizeSoFar = currentHashTableSize; | ||
| } | ||
| } |
This comment has been minimized.
This comment has been minimized.
| if (IgnoreAssignmentOperatorInsideHashTable && tokenNode.Value.Kind == TokenKind.Equals) | ||
| { | ||
| var enclosingHashTables = tokenOperations.Ast.FindAll(a => a.Extent.StartOffset <= tokenNode.Value.Extent.StartOffset && a.Extent.EndOffset >= tokenNode.Value.Extent.EndOffset && a is HashtableAst, true); | ||
| if (enclosingHashTables.Count() > 0) | ||
| { | ||
| Ast innermostEnclosingHashTable = enclosingHashTables.First(); | ||
| int smallestSizeSoFar = int.MaxValue; | ||
| foreach (var hashTable in enclosingHashTables){ | ||
| int currentHashTableSize = hashTable.Extent.EndOffset - hashTable.Extent.StartOffset; | ||
| if (currentHashTableSize < smallestSizeSoFar) | ||
| { | ||
| innermostEnclosingHashTable = hashTable; | ||
| smallestSizeSoFar = currentHashTableSize; | ||
| } | ||
| } | ||
| if (innermostEnclosingHashTable.Extent.StartLineNumber != innermostEnclosingHashTable.Extent.EndLineNumber) | ||
| { | ||
| continue; | ||
| } | ||
| } | ||
| } |
This comment has been minimized.
This comment has been minimized.
rjmholt
Aug 7, 2020
Member
So with this strategy, we collect all the hashtable ASTs that contain the token offset, and then try to find the smallest one. That works, but I think a better strategy is to:
- Find the smallest AST that contains the token offset
- Test whether that AST is a
HashtableAst(since the=in HashtableAst key-value pairs is directly subordinate to that AST)
That way there's no need to sift through multiple ASTs reflecting on them to work out whether they're HashtableAsts, only the one containing the = operator.
One way to find the smallest containing AST is to do as you've done with the FindAll() method and sift through the returned ASTs looking for the one with the smallest extent. A slightly more efficient way is to implement a visitor like the one in PowerShell/vscode-powershell#2860 (comment).
| /// <summary> | ||
| /// Provides an efficient way to find the position in the AST corresponding to a given script position. | ||
| /// </summary> | ||
| public class FindAstPostitionVisitor : AstVisitor2 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
daviesj
Aug 11, 2020
Author
Wow you are quick. Was just going to add a comment asking for a bit of direction but I am working on about 6 different things today.
Wasn't sure about the following:
- Whether I had put the visitor in the right spot in the code. The structure and conventions of this repository are a bit hard for me to puzzle out. Will go ahead put this in a separate file.
- How much testing is needed for the visitor functionality? I only created one test that it does what I need for the UseConsistentWhitespace rule. Seems to me that adding a generic function like this with as much code as it is going to be should have more tests than that but no idea where to go with that.
This comment has been minimized.
This comment has been minimized.
rjmholt
Aug 11, 2020
Member
but I am working on about 6 different things today.
No worries! There's no rush at all -- feel free to take your time
The structure and conventions of this repository are a bit hard for me to puzzle out.
Agreed, they're not great. Almost anywhere in Engine should be fine. We can work out where specifically before merging.
How much testing is needed for the visitor functionality? I only created one test that it does what I need for the UseConsistentWhitespace rule. Seems to me that adding a generic function like this with as much code as it is going to be should have more tests than that but no idea where to go with that.
Yeah, ideally if it's a general purpose visitor it should have a bunch of tests, but all our testing is in Pester today which makes that harder. For now, if it's only used in the one rule, it should be enough to make sure that rule is thoroughly tested
| public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) | ||
| { | ||
| return Visit(scriptBlockAst); | ||
| } | ||
|
|
||
| public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) | ||
| { | ||
| return Visit(namedBlockAst); | ||
| } | ||
|
|
||
| public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) | ||
| { | ||
| return Visit(assignmentStatementAst); | ||
| } | ||
|
|
||
| public override AstVisitAction VisitCommandExpression(CommandExpressionAst commandExpressionAst) | ||
| { | ||
| return Visit(commandExpressionAst); | ||
| } | ||
|
|
||
| public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) | ||
| { | ||
| return Visit(hashtableAst); | ||
| } |
This comment has been minimized.
This comment has been minimized.
rjmholt
Aug 11, 2020
Member
Given its general name, this visitor should probably have overrides for all AST types so that it will reliably work for all position visits. The alternative is to implement this class as a private nested class for the relevant rule.
This comment has been minimized.
This comment has been minimized.
daviesj
Aug 11, 2020
Author
Fully intend to implement all overrides. Just didn't want to produce that much code before I knew I was barking up the right tree. It flummoxed me a bit when I first decided to work on this that there is no good way to search a PowerShell AST based on script position without writing 200+ lines of code. That is why I had tried the simpler solution first (that I agree with you was not ideal by a long shot).
This comment has been minimized.
This comment has been minimized.
rjmholt
Aug 11, 2020
•
Member
Well there is another way, which is to use FindAll() instead:
IEnumerable<Ast> astsContainingToken = scriptAst.FindAll(foundAst => ContainsTokenExtent(token, foundAst), /* search through nested ASTs -- I can't remember the correct parameter name */ true);
// Find the containing AST with the smallest extent
Ast smallestContainingAst = astsContainingToken.First();
foreach (Ast containingAst in astsContainingToken)
{
if (HasSmallerExtent(smallestContainingAst, containingAst))
{
smallestContainingAst = containingAst;
}
}Naturally you'd need to write the methods I've used there, but they basically contain logic you've already written
This comment has been minimized.
This comment has been minimized.
daviesj
Aug 12, 2020
Author
I did try something like that but ran into a problem because apparently Ast's can contain another of exactly the same size so there isn't always a smallest. At that point I figured as many issues as I have run into so far trying to use FindAll(), it would be better just to go the visitor route.
|
Not sure if I have the preprocessor version directives just right in FindAstPositionVisitor.cs. I was puzzled that there isn't a |

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.

daviesj commentedAug 7, 2020
•
edited
PR Summary
Fix #769 instead of creating a workaround. This creates an option on the UseConsistentWhitespace rule to ignore the assignment operator inside a multi-line hash table, making it compatible with the AlignAssignmentStatement rule. Also this enables the new option in the code formatting settings presets since they all enable the AlignAssignmentStatement rule. If I were a bit smarter, I would also automatically enable this option if AlignAssignmentStatement is enabled but I do not see how to do that.
PR Checklist`
.cs,.ps1and.psm1files have the correct copyright headerWIP:to the beginning of the title and remove the prefix when the PR is ready.