Rules/Test-ForSlowScript.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
function Test-ForSlowScript
{
    #region ScriptTokenValidation Parameter Statement
    [OutputType([Nullable])]
    param(
    <#
    This parameter will contain the tokens in the script, and will be automatically
    provided when this command is run within ScriptCop.
     
    This parameter should not be used directly, except for testing purposes.
    #>

    [Parameter(ParameterSetName='TestScriptToken',
        Mandatory=$true,
        ValueFromPipelineByPropertyName=$true)]
    [Management.Automation.PSToken[]]
    $ScriptToken,
    
    <#
    This parameter will contain the command that was tokenized, and will be automatically
    provided when this command is run within ScriptCop.
     
    This parameter should not be used directly, except for testing purposes.
    #>

    [Parameter(ParameterSetName='TestScriptToken',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
    [Management.Automation.CommandInfo]
    $ScriptTokenCommand,
    
    <#
    This parameter contains the raw text of the script, and will be automatically
    provided when this command is run within ScriptCop
     
    This parameter should not be used directly, except for testing purposes.
    #>

    [Parameter(ParameterSetName='TestScriptToken',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
    [string]
    $ScriptText
    )
    #endregion ScriptTokenValidation Parameter Statement
    
    process {           
        # Find any GroupStart tokens that are @(,
        # - then find the preceeding variable.
        # - then find all instances of that variable using +=

        $PotentiallySlowVariables = @{}


        $VariablesIKnowToBeSlow = New-Object Collections.ArrayList
        

        $GetContentAliases = Get-Alias | 
            Where-Object {$_.ResolvedCommand -like "Get-Content" } |
            Select-Object -ExpandProperty Name

        $AddContentAliases = Get-Alias | 
            Where-Object {$_.ResolvedCommand -like "Add-Content" } |
            Select-Object -ExpandProperty Name
        for ($i = 0; $i -lt $ScriptToken.count; $i++) {
            $t = $ScriptToken[$i]
            if ($t.Type -eq 'GroupStart' -and $t.Content -eq '@(') {
                # List Start Found


                for ($j = $i -1; $j -ge 0; $j--) {
                    if ($ScriptToken[$j].Type -eq 'Variable') {
                        $PotentiallySlowVariables[$scriptToken[$j].Content]= $scriptToken[$j]
                        break
                    }
                }
            }


            if ($t.Type -eq 'Operator' -and $t.Content -eq '+=') {
                # += found. Walk back and see if we know the variable
                
                for ($j = $i -1; $j -ge 0; $j--) {
                    if ($ScriptToken[$j].Type -eq 'Variable') {
                        if ($PotentiallySlowVariables.ContainsKey($ScriptToken[$j].Content)) {
                            # We've found an addition we don't like
                            # Tack this variable onto the list of errors
                            $null = $VariablesIKnowToBeSlow.Add($ScriptToken[$j].Content)
                        }
                        break
                    }
                }
                
                
            }


            if (($t.Type -eq 'Command' -and $t.Content -eq 'Get-Content') -or
                ($t.Type -eq 'Command' -and $GetContentAliases -contains $t.Content)) {
                Write-Error "Get-Content is not exactly fast. [IO.File]::ReadAllText or [IO.File]::ReadAllBytes would be a better choice."                        
            }


            if (($t.Type -eq 'Command' -and $t.Content -eq 'Add-Content') -or 
                ($t.Type -eq 'Command' -and $AddContentAliases -contains $t.Content)) {
                Write-Error "Add-Content is abysmally slow. Try [io.file]::AppendAllText"                        
            }
        
        }    
        
        
        foreach ($var in $VariablesIKnowToBeSlow) {
            Write-Error "$var is an array, and you append to it with +=. Since arrays in .NET are immutable (they never change), this causes the list to be recreated, and gets REALLY slow. A better alternative would be using an ArrayList."                        
        }                         

    }
   
}