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

PSUseConsistantWhiteSpace #2089

Open
AlexandraDorey opened this issue Mar 19, 2025 · 1 comment
Open

PSUseConsistantWhiteSpace #2089

AlexandraDorey opened this issue Mar 19, 2025 · 1 comment

Comments

@AlexandraDorey
Copy link

AlexandraDorey commented Mar 19, 2025

Steps to reproduce

Attaching our PSD fille for the analyzer config

We have

PSUseConsistentWhitespace with IgnoreAssignmentOperatorInsideHashTable = $true
PSAlignAssignmentStatement with Enabled = $true and CheckHashTable = $true

It generally works as advertised... except...

When a using statement is present... we get a "Use space before and after binary and assignment operators.PSScriptAnalyzer(PSUseConsistentWhitespace)" error.

This does not work ...

using namespace DoesNotMatter

$SoSad = @{
    not     = 'other'
    theSame = 'casting sucks maybe?'
}

This works in our setup...

$SoHappy= @{
    not     = 'other'
    theSame = 'casting sucks.'
}

``
![Image](https://github.com/user-attachments/assets/d7e4d67f-8fdb-4ab3-8ede-a496db5c7f6b)

Expected behavior
-----------------
 
No linting error.

Actual behavior
---------------

Seems to check inside hashtables when a using statement of ANY kind is present. The fixer works still though so it doesn't feel like something inherent with the type, maybe something to do with how using are tokenized? 

D:\Symphony\devops-scripts\Symphony\Symphony.Azdo\Happy.ps1
Environment dataD:\Symphony\devops-scripts\Symphony\Symphony.Azdo\Sad.ps1
----------------

Present in a bunch of envs including our build farm...and using the latest version (1.24.0)

Attaching file

[PSScriptAnalyzerSettings.txt](https://github.com/user-attachments/files/19352089/PSScriptAnalyzerSettings.txt)
@AlexandraDorey
Copy link
Author

AlexandraDorey commented Mar 20, 2025

Well that was interesting...->

I pulled the code to see what was happening cause I was curious, it seems to come down to somewhat weird behavior of the UsingStatementAst vs other kinds of Asts like say ScriptBlockAst which flip the SkipChildren to a Continue inside it's the internalvisit but the internal visit of the UsingStatementAst just passes the SkipChildren through instead of flipping it to a continue when visiting a

InternalVisit of UsingStatementAst

vs

Internal Visit of NamedBlock

I can't tell if this is a bug in the system automation stuff or some weird expected behavior, if you parse a file with using statements, once the visitor hits the script block, sets that as it's visit and then exits out after hitting the UsingStatementAst (usually the next one in my testing),

Investigation reproduction:

Input to pester test passing:

#using namespace somethin

$ht = @{
    variable = 3
    other    = 4
}

$alpha = @{
    beta  =
    delta = 4
}
'@

Passing output of ast tree:

ROOT <- ScriptBlockAst : 0 to 112
ScriptBlockAst <- NamedBlockAst : 27 to 112
NamedBlockAst <- AssignmentStatementAst : 27 to 71
AssignmentStatementAst <- VariableExpressionAst : 27 to 30
AssignmentStatementAst <- CommandExpressionAst : 33 to 71
CommandExpressionAst <- HashtableAst : 33 to 71
HashtableAst <- StringConstantExpressionAst : 40 to 48
HashtableAst <- PipelineAst : 51 to 52
PipelineAst <- CommandExpressionAst : 51 to 52
CommandExpressionAst <- ConstantExpressionAst : 51 to 52
HashtableAst <- StringConstantExpressionAst : 57 to 62
HashtableAst <- PipelineAst : 68 to 69
PipelineAst <- CommandExpressionAst : 68 to 69
CommandExpressionAst <- ConstantExpressionAst : 68 to 69
NamedBlockAst <- AssignmentStatementAst : 73 to 112
AssignmentStatementAst <- VariableExpressionAst : 73 to 79
AssignmentStatementAst <- CommandExpressionAst : 82 to 112
CommandExpressionAst <- HashtableAst : 82 to 112
HashtableAst <- StringConstantExpressionAst : 89 to 93
HashtableAst <- PipelineAst : 101 to 110
PipelineAst <- CommandAst : 101 to 110
CommandAst <- StringConstantExpressionAst : 101 to 106
CommandAst <- StringConstantExpressionAst : 107 to 108
CommandAst <- ConstantExpressionAst : 109 to 110

Input script when failing:

using namespace something

$ht = @{
    variable = 3
    other    = 4
}

$alpha = @{
    beta  =
    delta = 4
}
'@

Failing output of ast tree:

ROOT <- ScriptBlockAst : 0 to 112
ScriptBlockAst <- UsingStatementAst : 0 to 25
UsingStatementAst <- StringConstantExpressionAst : 16 to 25
ScriptBlockAst <- NamedBlockAst : 27 to 112
NamedBlockAst <- AssignmentStatementAst : 27 to 71
AssignmentStatementAst <- VariableExpressionAst : 27 to 30
AssignmentStatementAst <- CommandExpressionAst : 33 to 71
CommandExpressionAst <- HashtableAst : 33 to 71
HashtableAst <- StringConstantExpressionAst : 40 to 48
HashtableAst <- PipelineAst : 51 to 52
PipelineAst <- CommandExpressionAst : 51 to 52
CommandExpressionAst <- ConstantExpressionAst : 51 to 52
HashtableAst <- StringConstantExpressionAst : 57 to 62
HashtableAst <- PipelineAst : 68 to 69
PipelineAst <- CommandExpressionAst : 68 to 69
CommandExpressionAst <- ConstantExpressionAst : 68 to 69
NamedBlockAst <- AssignmentStatementAst : 73 to 112
AssignmentStatementAst <- VariableExpressionAst : 73 to 79
AssignmentStatementAst <- CommandExpressionAst : 82 to 112
CommandExpressionAst <- HashtableAst : 82 to 112
HashtableAst <- StringConstantExpressionAst : 89 to 93
HashtableAst <- PipelineAst : 101 to 110
PipelineAst <- CommandAst : 101 to 110
CommandAst <- StringConstantExpressionAst : 101 to 106
CommandAst <- StringConstantExpressionAst : 107 to 108
CommandAst <- ConstantExpressionAst : 109 to 110

I added some logging inside the Visit function of the FindAstPositionVisitor.cs class

 private AstVisitAction Visit(Ast ast)
        {
            Console.WriteLine($"Visiting {ast.GetType().Name}");
            if (ast.Extent.StartOffset > searchPosition.Offset || ast.Extent.EndOffset <= searchPosition.Offset)
            {
                Console.WriteLine($"   Skipping Kids...");
                return AstVisitAction.SkipChildren;
            }
            Console.WriteLine($"   Setting As Position...");
            AstPosition = ast;
            return AstVisitAction.Continue;
        }

and the output when passing was:

Visiting ScriptBlockAst
   Setting As Position...
Visiting NamedBlockAst
   Setting As Position...
Visiting AssignmentStatementAst
   Skipping Kids...
Visiting AssignmentStatementAst
   Setting As Position...
Visiting VariableExpressionAst
   Skipping Kids...
Visiting CommandExpressionAst
   Setting As Position...
Visiting HashtableAst
   Setting As Position...
Visiting StringConstantExpressionAst
   Skipping Kids...
Visiting PipelineAst
   Skipping Kids...
Done visiting...

and when failing it was:

Visiting ScriptBlockAst
   Setting As Position...
Visiting UsingStatementAst
   Skipping Kids...
Done visiting...

as a result the type of the AST returned is a ScriptBlockAst instead of a HashTableAst here the UseConsistantWhiteSpace.cs and an error is reported instead of being skipped...

Quick Fix:

I don't believe PSScriptAnalyzer does anything related using statements currently? if that's accurate then simply returning AstVisitAction.Continue instead of passing the UsingStatementAst down to your visit function function fixed it locally for me.

Otherwise some specialized logic inside your Visit function will likely be required. Of course it could also be it's a bug in the system automation but I feel like that'll be longer fix unless y'all are on the same team :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant