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;