From 3a47f4af6b3c0869f27557fd1b596a8271383f83 Mon Sep 17 00:00:00 2001
From: Joel Davies <12704625+daviesj@users.noreply.github.com>
Date: Tue, 20 Oct 2020 23:23:43 -0500
Subject: [PATCH 1/3] Add tests for more types of operators
---
Tests/Rules/UseConsistentWhitespace.tests.ps1 | 147 +++++++++++++++++-
1 file changed, 146 insertions(+), 1 deletion(-)
diff --git a/Tests/Rules/UseConsistentWhitespace.tests.ps1 b/Tests/Rules/UseConsistentWhitespace.tests.ps1
index 77d320830..5e96b4f2d 100644
--- a/Tests/Rules/UseConsistentWhitespace.tests.ps1
+++ b/Tests/Rules/UseConsistentWhitespace.tests.ps1
@@ -119,7 +119,7 @@ function foo($param) {
}
}
- Context "When there is whitespace around assignment and binary operators" {
+ Context "When there is whitespace around operators" {
BeforeAll {
$ruleConfiguration.CheckInnerBrace = $false
$ruleConfiguration.CheckOpenParen = $false
@@ -180,6 +180,151 @@ $x = $true -and
Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null
}
+ It "Should find a violation if there is no space around an arithmetic operator" {
+ $def = '$z = 3+4'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '+' ' + '
+ }
+
+ It "Should find a violation if there is no space around a bitwise operator" {
+ $def = '$value = 7-band3'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-band' ' -band '
+ }
+
+ It "Should find a violation if there is no space around an equality comparison operator" {
+ $def = '$obviously = 3-lt4'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-lt' ' -lt '
+ }
+
+ It "Should find a violation if there is no space around a matching operator" {
+ $def = '$shouldSend = $fromAddress-like"*@*.com"'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-like' ' -like '
+ }
+
+ It "Should find a violation if there is no space around replace operator" {
+ $def = '$a = "string"-replace"ing", "aight"'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-replace' ' -replace '
+ }
+
+ It "Should find a violation if there is no space around a containment operator" {
+ $def = 'if ("filename.txt"-in$FileList) { }'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-in' ' -in '
+ }
+
+ It "Should find a violation if there is no space around a type operator" {
+ $def = '$HoustonWeHaveAProblem = $a-isnot[System.Object]'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-isnot' ' -isnot '
+ }
+
+ It "Should find a violation if there is no space around a logical operator" {
+ $def = '$a = $b-xor$c'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-xor' ' -xor '
+ }
+
+ It "Should find a violation if there is no space around logical not operator but only on one side" {
+ $def = '$lie = (-not$true)'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '' ' '
+ }
+
+ It "Should find a violation if there is no space around redirection operator" {
+ $def = '"hi">>secretmessage.txt'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '>>' ' >> '
+ }
+
+ It "Should find a violation if there is no space around binary split operator" {
+ $def = '$numbers = "one:two:three"-split":"'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-split' ' -split '
+ }
+
+ It "Should find a violation if there is no space around unary join operator but only on one side" {
+ $def = 'ConvertFrom-Json (-join(dotnet gitversion))'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '' ' '
+ }
+
+ It "Should find a violation if there is no space around format operator" {
+ $def = '"{0:X}"-f88'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '-f' ' -f '
+ }
+
+ It "Should find violations if there is no space around ternary operator" -Skip:($PSVersionTable.PSVersion -lt '7.0') {
+ $def = '($a -is [System.Object])?3:4'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ $violations | Should -HaveCount 2
+ Test-CorrectionExtentFromContent $def $violations[0] 1 '?' ' ? '
+ Test-CorrectionExtentFromContent $def $violations[1] 1 ':' ' : '
+ }
+
+ It "Should not find a violation if there is no space around pipeline operator" {
+ $def = 'Get-It|Forget-It'
+ Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null
+ }
+
+ It "Should find a violation if there is no space around pipeline chain operator" -Skip:($PSVersionTable.PSVersion -lt '7.0') {
+ $def = 'Start-Service $ServiceName||Write-Error "Could not start $ServiceName"'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '||' ' || '
+ }
+
+ It "Should find a violation if there is no space around null coalescing operator" -Skip:($PSVersionTable.PSVersion -lt '7.0') {
+ $def = '${a}??3'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '??' ' ?? '
+ }
+
+ It "Should find a violation if there is no space around call operator" {
+ $def = '(&$ScriptFile)'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '' ' '
+ }
+
+ It "Should find a violation if there is no space around background operator" -Skip:($PSVersionTable.PSVersion -lt '6.0') {
+ $def = '(Get-LongThing&)'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '' ' '
+ }
+
+ It "Should find a violation if there is no space around dot sourcing operator" {
+ $def = '(.$ScriptFile)'
+ $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
+ Test-CorrectionExtentFromContent $def $violations 1 '' ' '
+ }
+
+ It "Should not find a violation if there is no space around member access operator" {
+ $def = '$PSVersionTable.PSVersion'
+ Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null
+ }
+
+ It "Should not find a violation if there is no space around comma operator" {
+ $def = '$somenumbers = 3,4,5'
+ Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null
+ }
+
+ It "Should not find a violation if there is no space around prefix operator" {
+ $def = '--$counter'
+ Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null
+ }
+
+ It "Should not find a violation if there is no space around postfix operator" {
+ $def = '$counter++'
+ Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null
+ }
+
+ It "Should not find a violation if there is no space around exclaim operator" {
+ $def = 'if(!$true){ "FALSE!@!!!!" }'
+ Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null
+ }
}
Context "When a comma is not followed by a space" {
From d3da1e420c4ef661a9ad629c9be5f1c3ac4a0020 Mon Sep 17 00:00:00 2001
From: Joel Davies <12704625+daviesj@users.noreply.github.com>
Date: Tue, 27 Oct 2020 03:12:46 -0500
Subject: [PATCH 2/3] Re-implement CheckOperator to be consistent between PS
versions & handle unary operators better
---
Engine/FindAstPositionVisitor.cs | 369 +++++++++++++++++++++++++++++++
Engine/TokenOperations.cs | 13 ++
Rules/UseConsistentWhitespace.cs | 107 +++++----
3 files changed, 452 insertions(+), 37 deletions(-)
create mode 100644 Engine/FindAstPositionVisitor.cs
diff --git a/Engine/FindAstPositionVisitor.cs b/Engine/FindAstPositionVisitor.cs
new file mode 100644
index 000000000..e8d5ada12
--- /dev/null
+++ b/Engine/FindAstPositionVisitor.cs
@@ -0,0 +1,369 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Management.Automation.Language;
+
+namespace Microsoft.Windows.PowerShell.ScriptAnalyzer
+{
+ ///
+ /// Provides an efficient way to find the position in the AST corresponding to a given script position.
+ ///
+#if !(PSV3 || PSV4)
+ internal class FindAstPositionVisitor : AstVisitor2
+#else
+ internal class FindAstPositionVisitor : AstVisitor
+#endif
+ {
+ private IScriptPosition searchPosition;
+
+ ///
+ /// Contains the position in the AST corresponding to the provided upon completion of the method.
+ ///
+ public Ast AstPosition { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class with the postition to search for.
+ ///
+ /// The script position to search for.
+ public FindAstPositionVisitor(IScriptPosition position)
+ {
+ this.searchPosition = position;
+ }
+
+ ///
+ /// Traverses the AST based on offests to find the leaf node which contains the provided .
+ /// This method implements the entire functionality of this visitor. All methods are overridden to simply invoke this one.
+ ///
+ /// Current AST node to process.
+ /// An indicating whether to visit children of the current node.
+ private AstVisitAction Visit(Ast ast)
+ {
+ if (ast.Extent.StartOffset > searchPosition.Offset || ast.Extent.EndOffset <= searchPosition.Offset)
+ {
+ return AstVisitAction.SkipChildren;
+ }
+ AstPosition = ast;
+ return AstVisitAction.Continue;
+ }
+
+ public override AstVisitAction VisitArrayExpression(ArrayExpressionAst arrayExpressionAst)
+ {
+ return Visit(arrayExpressionAst);
+ }
+
+ public override AstVisitAction VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst)
+ {
+ return Visit(arrayLiteralAst);
+ }
+
+ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst)
+ {
+ return Visit(assignmentStatementAst);
+ }
+
+ public override AstVisitAction VisitAttribute(AttributeAst attributeAst)
+ {
+ return Visit(attributeAst);
+ }
+
+ public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst)
+ {
+ return Visit(attributedExpressionAst);
+ }
+
+ public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
+ {
+ return Visit(binaryExpressionAst);
+ }
+
+ public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatementAst)
+ {
+ return Visit(blockStatementAst);
+ }
+
+ public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst)
+ {
+ return Visit(breakStatementAst);
+ }
+
+ public override AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst)
+ {
+ return Visit(catchClauseAst);
+ }
+
+ public override AstVisitAction VisitCommand(CommandAst commandAst)
+ {
+ return Visit(commandAst);
+ }
+
+ public override AstVisitAction VisitCommandExpression(CommandExpressionAst commandExpressionAst)
+ {
+ return Visit(commandExpressionAst);
+ }
+
+ public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst)
+ {
+ return Visit(commandParameterAst);
+ }
+
+ public override AstVisitAction VisitConstantExpression(ConstantExpressionAst constantExpressionAst)
+ {
+ return Visit(constantExpressionAst);
+ }
+
+ public override AstVisitAction VisitContinueStatement(ContinueStatementAst continueStatementAst)
+ {
+ return Visit(continueStatementAst);
+ }
+
+ public override AstVisitAction VisitConvertExpression(ConvertExpressionAst convertExpressionAst)
+ {
+ return Visit(convertExpressionAst);
+ }
+
+ public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst)
+ {
+ return Visit(dataStatementAst);
+ }
+
+ public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst)
+ {
+ return Visit(doUntilStatementAst);
+ }
+
+ public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst)
+ {
+ return Visit(doWhileStatementAst);
+ }
+
+ public override AstVisitAction VisitErrorExpression(ErrorExpressionAst errorExpressionAst)
+ {
+ return Visit(errorExpressionAst);
+ }
+
+ public override AstVisitAction VisitErrorStatement(ErrorStatementAst errorStatementAst)
+ {
+ return Visit(errorStatementAst);
+ }
+
+ public override AstVisitAction VisitExitStatement(ExitStatementAst exitStatementAst)
+ {
+ return Visit(exitStatementAst);
+ }
+
+ public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst)
+ {
+ return Visit(expandableStringExpressionAst);
+ }
+
+ public override AstVisitAction VisitFileRedirection(FileRedirectionAst fileRedirectionAst)
+ {
+ return Visit(fileRedirectionAst);
+ }
+
+ public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst)
+ {
+ return Visit(forEachStatementAst);
+ }
+
+ public override AstVisitAction VisitForStatement(ForStatementAst forStatementAst)
+ {
+ return Visit(forStatementAst);
+ }
+
+ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
+ {
+ return Visit(functionDefinitionAst);
+ }
+
+ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst)
+ {
+ return Visit(hashtableAst);
+ }
+
+ public override AstVisitAction VisitIfStatement(IfStatementAst ifStatementAst)
+ {
+ return Visit(ifStatementAst);
+ }
+
+ public override AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpressionAst)
+ {
+ return Visit(indexExpressionAst);
+ }
+
+ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst)
+ {
+ return Visit(invokeMemberExpressionAst);
+ }
+
+ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst)
+ {
+ return Visit(memberExpressionAst);
+ }
+
+ public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst)
+ {
+ return Visit(mergingRedirectionAst);
+ }
+
+ public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst)
+ {
+ return Visit(namedAttributeArgumentAst);
+ }
+
+ public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst)
+ {
+ return Visit(namedBlockAst);
+ }
+
+ public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst)
+ {
+ return Visit(paramBlockAst);
+ }
+
+ public override AstVisitAction VisitParameter(ParameterAst parameterAst)
+ {
+ return Visit(parameterAst);
+ }
+
+ public override AstVisitAction VisitParenExpression(ParenExpressionAst parenExpressionAst)
+ {
+ return Visit(parenExpressionAst);
+ }
+
+ public override AstVisitAction VisitPipeline(PipelineAst pipelineAst)
+ {
+ return Visit(pipelineAst);
+ }
+
+ public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst)
+ {
+ return Visit(returnStatementAst);
+ }
+
+ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst)
+ {
+ return Visit(scriptBlockAst);
+ }
+
+ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
+ {
+ return Visit(scriptBlockExpressionAst);
+ }
+
+ public override AstVisitAction VisitStatementBlock(StatementBlockAst statementBlockAst)
+ {
+ return Visit(statementBlockAst);
+ }
+
+ public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst)
+ {
+ return Visit(stringConstantExpressionAst);
+ }
+
+ public override AstVisitAction VisitSubExpression(SubExpressionAst subExpressionAst)
+ {
+ return Visit(subExpressionAst);
+ }
+
+ public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst)
+ {
+ return Visit(switchStatementAst);
+ }
+
+ public override AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatementAst)
+ {
+ return Visit(throwStatementAst);
+ }
+
+ public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst)
+ {
+ return Visit(trapStatementAst);
+ }
+
+ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst)
+ {
+ return Visit(tryStatementAst);
+ }
+
+ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst)
+ {
+ return Visit(typeConstraintAst);
+ }
+
+ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst)
+ {
+ return Visit(typeExpressionAst);
+ }
+
+ public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst)
+ {
+ return Visit(unaryExpressionAst);
+ }
+
+ public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst)
+ {
+ return Visit(usingExpressionAst);
+ }
+
+ public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
+ {
+ return Visit(variableExpressionAst);
+ }
+
+ public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst)
+ {
+ return Visit(whileStatementAst);
+ }
+
+#if !(PSV3 || PSV4)
+ public override AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst)
+ {
+ return Visit(baseCtorInvokeMemberExpressionAst);
+ }
+
+ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst)
+ {
+ return Visit(configurationDefinitionAst);
+ }
+
+ public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst)
+ {
+ return Visit(dynamicKeywordStatementAst);
+ }
+
+ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst)
+ {
+ return Visit(functionMemberAst);
+ }
+
+ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst)
+ {
+ return Visit(propertyMemberAst);
+ }
+
+ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst)
+ {
+ return Visit(typeDefinitionAst);
+ }
+
+ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst)
+ {
+ return Visit(usingStatementAst);
+ }
+#endif
+
+#if !(NET452 || PSV6) // NET452 includes V3,4,5
+ public override AstVisitAction VisitPipelineChain(PipelineChainAst pipelineChainAst)
+ {
+ return Visit(pipelineChainAst);
+ }
+
+ public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
+ {
+ return Visit(ternaryExpressionAst);
+ }
+#endif
+
+ }
+}
\ No newline at end of file
diff --git a/Engine/TokenOperations.cs b/Engine/TokenOperations.cs
index 0a47ab3cc..fa9a1978a 100644
--- a/Engine/TokenOperations.cs
+++ b/Engine/TokenOperations.cs
@@ -232,5 +232,18 @@ private bool OnSameLine(Token token1, Token token2)
{
return token1.Extent.StartLineNumber == token2.Extent.EndLineNumber;
}
+
+ ///
+ /// Finds the position of a given token in the AST.
+ ///
+ /// The to search for.
+ /// The Ast node directly containing the provided .
+ public Ast GetAstPosition(Token token)
+ {
+ FindAstPositionVisitor findAstVisitor = new FindAstPositionVisitor(token.Extent.StartScriptPosition);
+ ast.Visit(findAstVisitor);
+ return findAstVisitor.AstPosition;
+ }
+
}
}
diff --git a/Rules/UseConsistentWhitespace.cs b/Rules/UseConsistentWhitespace.cs
index aad4d3f46..80117f1eb 100644
--- a/Rules/UseConsistentWhitespace.cs
+++ b/Rules/UseConsistentWhitespace.cs
@@ -186,15 +186,6 @@ public override SourceType GetSourceType()
return SourceType.Builtin;
}
- private bool IsOperator(Token token)
- {
- return TokenTraits.HasTrait(token.Kind, TokenFlags.AssignmentOperator)
- || TokenTraits.HasTrait(token.Kind, TokenFlags.BinaryPrecedenceAdd)
- || TokenTraits.HasTrait(token.Kind, TokenFlags.BinaryPrecedenceMultiply)
- || token.Kind == TokenKind.AndAnd
- || token.Kind == TokenKind.OrOr;
- }
-
private string GetError(ErrorKind kind)
{
switch (kind)
@@ -513,46 +504,88 @@ private bool IsPreviousTokenOnSameLineAndApartByWhitespace(LinkedListNode
private IEnumerable FindOperatorViolations(TokenOperations tokenOperations)
{
- foreach (var tokenNode in tokenOperations.GetTokenNodes(IsOperator))
+ foreach (LinkedListNode tokenNode in tokenOperations.GetTokenNodes((t)=>true))
{
- if (tokenNode.Previous == null
- || tokenNode.Next == null
- || tokenNode.Value.Kind == TokenKind.DotDot)
- {
+ bool tokenHasUnaryFlag = TokenTraits.HasTrait(tokenNode.Value.Kind, TokenFlags.UnaryOperator);
+ bool tokenHasBinaryFlag = TokenTraits.HasTrait(tokenNode.Value.Kind, TokenFlags.BinaryOperator);
+ bool checkLeftSide = false;
+ bool checkRightSide = false;
+ bool operatorIsPrefixOrPostfix = false;
+
+ // Exclude operators handled by other UseConsistentWhitespace rule options
+ if (tokenNode.Value.Kind == TokenKind.DotDot
+ || tokenNode.Value.Kind == TokenKind.Comma) {
continue;
}
-
- // exclude unary operator for cases like $foo.bar(-$Var)
- if (TokenTraits.HasTrait(tokenNode.Value.Kind, TokenFlags.UnaryOperator) &&
- tokenNode.Previous.Value.Kind == TokenKind.LParen &&
- tokenNode.Next.Value.Kind == TokenKind.Variable)
+ // First check operators that have unary flag (may be unary or binary)
+ else if (tokenHasUnaryFlag)
+ {
+ Ast operatorAst = tokenOperations.GetAstPosition(tokenNode.Value);
+ // If both unary and binary flags are set, check type of AST node to determine which it is in this case.
+ if (tokenHasBinaryFlag && operatorAst is BinaryExpressionAst)
+ {
+ checkLeftSide = true;
+ checkRightSide = true;
+ }
+ else // Token must be unary operator.
+ {
+ operatorIsPrefixOrPostfix = TokenTraits.HasTrait(tokenNode.Value.Kind, TokenFlags.PrefixOrPostfixOperator)
+ || tokenNode.Value.Kind == TokenKind.Minus
+ || tokenNode.Value.Kind == TokenKind.Exclaim;
+ // If token and its AST node start at same position, operand is on the right.
+ if (tokenNode.Value.Extent.StartOffset == operatorAst.Extent.StartOffset)
+ {
+ checkRightSide = true;
+ }
+ else
+ {
+ checkLeftSide = true;
+ }
+ }
+ }
+ // Handle operators that are definitely binary
+ else if (tokenHasBinaryFlag // binary flag is set but not unary
+ // include other (non-expression) binary operators
+ || TokenTraits.HasTrait(tokenNode.Value.Kind, TokenFlags.AssignmentOperator)
+ || tokenNode.Value.Kind == TokenKind.AndAnd
+ || tokenNode.Value.Kind == TokenKind.OrOr
+ ) {
+ checkLeftSide = true;
+ checkRightSide = true;
+ }
+ else // Token is not an operator
{
continue;
}
- var hasWhitespaceBefore = IsPreviousTokenOnSameLineAndApartByWhitespace(tokenNode);
- var hasWhitespaceAfter = tokenNode.Next.Value.Kind == TokenKind.NewLine
- || IsPreviousTokenOnSameLineAndApartByWhitespace(tokenNode.Next);
-
- if (!hasWhitespaceAfter || !hasWhitespaceBefore)
+ if (!operatorIsPrefixOrPostfix)
{
- yield return new DiagnosticRecord(
- GetError(ErrorKind.Operator),
- tokenNode.Value.Extent,
- GetName(),
- GetDiagnosticSeverity(),
- tokenOperations.Ast.Extent.File,
- null,
- GetCorrections(
- tokenNode.Previous.Value,
- tokenNode.Value,
- tokenNode.Next.Value,
- hasWhitespaceBefore,
- hasWhitespaceAfter));
+ bool leftSideOK = !checkLeftSide || IsPreviousTokenOnSameLineAndApartByWhitespace(tokenNode);
+
+ bool rightSideOK = !checkRightSide || tokenNode.Next.Value.Kind == TokenKind.NewLine
+ || IsPreviousTokenOnSameLineAndApartByWhitespace(tokenNode.Next);
+
+ if (!leftSideOK || !rightSideOK)
+ {
+ yield return new DiagnosticRecord(
+ GetError(ErrorKind.Operator),
+ tokenNode.Value.Extent,
+ GetName(),
+ GetDiagnosticSeverity(),
+ tokenOperations.Ast.Extent.File,
+ null,
+ GetCorrections(
+ tokenNode.Previous?.Value,
+ tokenNode.Value,
+ tokenNode.Next?.Value,
+ leftSideOK,
+ rightSideOK));
+ }
}
}
}
+
private List GetCorrections(
Token prevToken,
Token token,
From 3943377c0bdcc582aeef86dd1310551c459847bc Mon Sep 17 00:00:00 2001
From: Joel Davies <12704625+daviesj@users.noreply.github.com>
Date: Thu, 22 Oct 2020 15:24:21 -0500
Subject: [PATCH 3/3] Check more operators
---
Rules/UseConsistentWhitespace.cs | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/Rules/UseConsistentWhitespace.cs b/Rules/UseConsistentWhitespace.cs
index 80117f1eb..6b202d55b 100644
--- a/Rules/UseConsistentWhitespace.cs
+++ b/Rules/UseConsistentWhitespace.cs
@@ -547,12 +547,29 @@ private IEnumerable FindOperatorViolations(TokenOperations tok
else if (tokenHasBinaryFlag // binary flag is set but not unary
// include other (non-expression) binary operators
|| TokenTraits.HasTrait(tokenNode.Value.Kind, TokenFlags.AssignmentOperator)
+ || tokenNode.Value.Kind == TokenKind.Redirection
|| tokenNode.Value.Kind == TokenKind.AndAnd
|| tokenNode.Value.Kind == TokenKind.OrOr
+#if !(NET452 || PSV6) // include both parts of ternary operator but only for PS7+
+ || TokenTraits.HasTrait(tokenNode.Value.Kind, TokenFlags.TernaryOperator)
+ || tokenNode.Value.Kind == TokenKind.Colon
+#endif
) {
checkLeftSide = true;
checkRightSide = true;
}
+ // Treat call and dot source operators as unary with operand on right.
+ else if ((tokenNode.Value.Kind == TokenKind.Dot || tokenNode.Value.Kind == TokenKind.Ampersand)
+ && tokenOperations.GetAstPosition(tokenNode.Value) is CommandAst)
+ {
+ checkRightSide = true;
+ }
+#if !(NET452) // Treat background operator as unary with operand on left (only exists in PS6+)
+ else if (tokenNode.Value.Kind == TokenKind.Ampersand)
+ {
+ checkLeftSide = true;
+ }
+#endif
else // Token is not an operator
{
continue;