@@ -339,8 +339,8 @@ enum MyEnum {
339339 }
340340 }
341341
342- Context " Informational severity" {
343- It " Should have Information severity" {
342+ Context " Rule severity" {
343+ It " Should have Warning severity" {
344344 $def = ' Add-Type -AssemblyName System.Windows.Forms'
345345 $violations = Invoke-ScriptAnalyzer - ScriptDefinition $def - Settings $settings
346346 $matchingViolations = $violations | Where-Object { $_.RuleName -eq $violationName }
@@ -350,27 +350,27 @@ enum MyEnum {
350350
351351 Context " When type constraints are used" {
352352 It " Should flag disallowed type constraint on parameter" {
353- $def = ' function Test { param([System.Net.WebClient]$Client ) }'
353+ $def = ' function Test { param([System.IO.File]$FileHelper ) }'
354354 $violations = Invoke-ScriptAnalyzer - ScriptDefinition $def - Settings $settings
355355 $matchingViolations = $violations | Where-Object { $_.RuleName -eq $violationName }
356356 $matchingViolations.Count | Should - BeGreaterThan 0
357- $matchingViolations [0 ].Message | Should - BeLike " *System.Net.WebClient *not permitted*"
357+ $matchingViolations [0 ].Message | Should - BeLike " *System.IO.File *not permitted*"
358358 }
359359
360360 It " Should flag disallowed type constraint on variable declaration" {
361- $def = ' [System.Net.WebClient]$client = $null'
361+ $def = ' [System.IO.File]$fileHelper = $null'
362362 $violations = Invoke-ScriptAnalyzer - ScriptDefinition $def - Settings $settings
363363 $matchingViolations = $violations | Where-Object { $_.RuleName -eq $violationName }
364364 $matchingViolations.Count | Should - BeGreaterThan 0
365- $matchingViolations [0 ].Message | Should - BeLike " *System.Net.WebClient *not permitted*"
365+ $matchingViolations [0 ].Message | Should - BeLike " *System.IO.File *not permitted*"
366366 }
367367
368368 It " Should flag disallowed type cast on variable assignment" {
369- $def = ' $client = [System.Net.WebClient ]$value'
369+ $def = ' $fileHelper = [System.IO.File ]$value'
370370 $violations = Invoke-ScriptAnalyzer - ScriptDefinition $def - Settings $settings
371371 $matchingViolations = $violations | Where-Object { $_.RuleName -eq $violationName }
372372 $matchingViolations.Count | Should - BeGreaterThan 0
373- $matchingViolations [0 ].Message | Should - BeLike " *System.Net.WebClient *not permitted*"
373+ $matchingViolations [0 ].Message | Should - BeLike " *System.IO.File *not permitted*"
374374 }
375375
376376 It " Should NOT flag allowed type constraint" {
@@ -441,8 +441,8 @@ $result = $client.DownloadString("http://example.com")
441441 $matchingViolations = $violations | Where-Object { $_.RuleName -eq $violationName }
442442 # Should flag both the type constraint AND the member access
443443 $matchingViolations.Count | Should - BeGreaterThan 1
444- # At least one violation should mention DownloadString
445- ($matchingViolations.Message | Where-Object { $_ -like " *DownloadString *" }).Count | Should - BeGreaterThan 0
444+ # At least one violation should mention ReadAllText
445+ ($matchingViolations.Message | Where-Object { $_ -like " *ReadAllText *" }).Count | Should - BeGreaterThan 0
446446 }
447447
448448 It " Should NOT flag method invocation on allowed types" {
@@ -458,19 +458,19 @@ function Test {
458458 }
459459
460460 It " Should NOT flag static method calls on disallowed types (already caught by type expression check)" {
461- $def = ' [System.Net.WebClient ]::New( )'
461+ $def = ' [System.IO.File ]::Exists("test.txt" )'
462462 $violations = Invoke-ScriptAnalyzer - ScriptDefinition $def - Settings $settings
463463 $matchingViolations = $violations | Where-Object { $_.RuleName -eq $violationName }
464464 # Should only flag once for the type expression, not for member access
465465 $matchingViolations.Count | Should - Be 1
466- $matchingViolations [0 ].Message | Should - BeLike " *System.Net.WebClient *"
466+ $matchingViolations [0 ].Message | Should - BeLike " *System.IO.File *"
467467 }
468468
469469 It " Should flag chained method calls on disallowed types" {
470470 $def = @'
471471function Test {
472- param([System.Net.WebClient]$Client )
473- $result = $Client.DownloadString("http://example.com").ToUpper ()
472+ param([System.IO.FileInfo]$FileHelper )
473+ $result = $FileHelper.OpenText().ReadToEnd ()
474474}
475475'@
476476 $violations = Invoke-ScriptAnalyzer - ScriptDefinition $def - Settings $settings
@@ -483,13 +483,13 @@ function Test {
483483 $def = @'
484484function Complex-Test {
485485 param(
486- [System.Net.WebClient]$WebClient ,
487- [System.Net.Sockets.TcpClient]$TcpClient ,
486+ [System.IO.File]$FileHelper ,
487+ [System.IO.Directory]$DirHelper ,
488488 [string]$SafeString
489489 )
490490
491- $data = $WebClient.DownloadData("http:// test.com ")
492- $TcpClient.Connect("localhost", 8080 )
491+ $data = $FileHelper.ReadAllBytes("C:\ test.bin ")
492+ $DirHelper.GetFiles("C:\temp" )
493493 $upper = $SafeString.ToUpper()
494494}
495495'@
@@ -526,8 +526,8 @@ Add-Type -TypeDefinition "public class Test { }"
526526 It " Should NOT flag disallowed types in signed scripts" {
527527 $scriptPath = Join-Path $tempPath " SignedWithDisallowedType.ps1"
528528 $scriptContent = @'
529- $client = New-Object System.Net.WebClient
530- $data = $client.DownloadString("http://example.com" )
529+ $fileHelper = New-Object System.IO.FileInfo("C:\test.txt")
530+ $data = $fileHelper.OpenText( )
531531
532532# SIG # Begin signature block
533533# MIIFFAYJKoZIhvcNAQcCoIIFBTCCBQECAQExCzAJ...
@@ -536,7 +536,7 @@ $data = $client.DownloadString("http://example.com")
536536 Set-Content - Path $scriptPath - Value $scriptContent
537537 $violations = Invoke-ScriptAnalyzer - Path $scriptPath - Settings $settings
538538 $typeViolations = $violations | Where-Object {
539- $_.RuleName -eq $violationName -and $_.Message -like " *WebClient *type*"
539+ $_.RuleName -eq $violationName -and $_.Message -like " *FileInfo *type*"
540540 }
541541 $typeViolations | Should - BeNullOrEmpty
542542 }
@@ -582,7 +582,7 @@ class MyClass {
582582 $scriptPath = Join-Path $tempPath " SignedWithBadParam.ps1"
583583 $scriptContent = @'
584584function Test {
585- param([System.Net.WebClient]$Client )
585+ param([System.IO.File]$FileHelper )
586586 Write-Output "Test"
587587}
588588
@@ -593,7 +593,7 @@ function Test {
593593 Set-Content - Path $scriptPath - Value $scriptContent
594594 $violations = Invoke-ScriptAnalyzer - Path $scriptPath - Settings $settings
595595 $paramViolations = $violations | Where-Object {
596- $_.RuleName -eq $violationName -and $_.Message -like " *WebClient *"
596+ $_.RuleName -eq $violationName -and $_.Message -like " *File *"
597597 }
598598 # Parameter type constraints should still be flagged
599599 $paramViolations.Count | Should - BeGreaterThan 0
@@ -643,5 +643,71 @@ function Test {
643643 $scriptModuleViolations.Count | Should - BeGreaterThan 0
644644 }
645645 }
646+
647+ Context " Performance with large scripts" {
648+ It " Should handle scripts with many typed variables and member invocations efficiently" {
649+ # This test verifies the O(N+M) cache optimization
650+ # Without caching, this would be O(N*M) and very slow
651+
652+ # Build a script with many typed variables and member invocations
653+ $scriptBuilder = [System.Text.StringBuilder ]::new()
654+ [void ]$scriptBuilder.AppendLine (' function Test-Performance {' )
655+ [void ]$scriptBuilder.AppendLine (' param([string]$Path)' )
656+
657+ # Add 30 typed variable assignments
658+ for ($i = 1 ; $i -le 30 ; $i ++ ) {
659+ [void ]$scriptBuilder.AppendLine (" [System.IO.File]`$ file$i = `$ null" )
660+ }
661+
662+ # Add 50 member invocations (testing cache reuse)
663+ for ($i = 1 ; $i -le 50 ; $i ++ ) {
664+ $varNum = ($i % 30 ) + 1
665+ [void ]$scriptBuilder.AppendLine (" `$ result$i = `$ file$varNum .ReadAllText(`$ Path)" )
666+ }
667+
668+ [void ]$scriptBuilder.AppendLine (' }' )
669+ $def = $scriptBuilder.ToString ()
670+
671+ # Measure performance
672+ $stopwatch = [System.Diagnostics.Stopwatch ]::StartNew()
673+ $violations = Invoke-ScriptAnalyzer - ScriptDefinition $def - Settings $settings
674+ $stopwatch.Stop ()
675+
676+ $matchingViolations = $violations | Where-Object { $_.RuleName -eq $violationName }
677+
678+ # Should detect violations (30 type constraints + 50 member accesses = 80+)
679+ $matchingViolations.Count | Should - BeGreaterThan 50
680+
681+ # Performance check: Should complete quickly (under 500ms)
682+ # With cache: O(N+M) = 30+50 = 80 operations
683+ # Without cache: O(N*M) = 50*30 = 1,500 operations (much slower)
684+ $stopwatch.ElapsedMilliseconds | Should - BeLessThan 500
685+ }
686+
687+ It " Should cache results per scope correctly" {
688+ # Test that cache is scoped properly and doesn't leak between functions
689+ $def = @'
690+ function Function1 {
691+ [System.IO.File]$file1 = $null
692+ $result1 = $file1.ReadAllText("C:\test1.txt")
646693}
647694
695+ function Function2 {
696+ [System.IO.Directory]$file1 = $null
697+ $result2 = $file1.GetFiles("C:\temp")
698+ }
699+ '@
700+ $violations = Invoke-ScriptAnalyzer - ScriptDefinition $def - Settings $settings
701+ $matchingViolations = $violations | Where-Object { $_.RuleName -eq $violationName }
702+
703+ # Should detect violations in both functions
704+ # Each function has: 1 type constraint + 1 member access = 2 violations each
705+ $matchingViolations.Count | Should - BeGreaterOrEqual 4
706+
707+ # Verify both File and Directory are mentioned
708+ $messages = $matchingViolations.Message -join ' '
709+ $messages | Should - BeLike " *File*"
710+ $messages | Should - BeLike " *Directory*"
711+ }
712+ }
713+ }
0 commit comments