-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53 from PoshCode/feature/usings
Feature/usings
- Loading branch information
Showing
5 changed files
with
228 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
function MoveUsingStatements { | ||
<# | ||
.SYNOPSIS | ||
A command to comment out and copy to the top of the file the Using Statements | ||
.DESCRIPTION | ||
When all files are merged together, the Using statements from individual files | ||
don't necessarily end up at the beginning of the PSM1, creating Parsing Errors. | ||
This function uses AST to comment out those statements (to preserver line numbering) | ||
and insert them (conserving order) at the top of the script. | ||
Should the merged RootModule already have errors not related to the Using statements, | ||
or no errors caused by misplaced Using statements, this steps is skipped. | ||
If moving (comment & copy) the Using statements introduce parsing errors to the script, | ||
those changes won't be applied to the file. | ||
#> | ||
[CmdletBinding()] | ||
Param( | ||
# Path to the PSM1 file to amend | ||
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)] | ||
$RootModule, | ||
|
||
# The encoding defaults to UTF8 (or UTF8NoBom on Core) | ||
[Parameter(DontShow)] | ||
[string]$Encoding = $(if ($IsCoreCLR) { "UTF8NoBom" } else { "UTF8" }) | ||
) | ||
|
||
$ParseError = $null | ||
$AST = [System.Management.Automation.Language.Parser]::ParseFile( | ||
$RootModule, | ||
[ref]$null, | ||
[ref]$ParseError | ||
) | ||
|
||
# Avoid modifying the file if there's no Parsing Error caused by Using Statements or other errors | ||
if (!$ParseError.Where{$_.ErrorId -eq 'UsingMustBeAtStartOfScript'}) { | ||
Write-Debug "No Using Statement Error found." | ||
return | ||
} | ||
# Avoid modifying the file if there's other parsing errors than Using Statements misplaced | ||
if ($ParseError.Where{$_.ErrorId -ne 'UsingMustBeAtStartOfScript'}) { | ||
Write-Warning "Parsing errors found. Skipping Moving Using statements." | ||
return | ||
} | ||
|
||
# Find all Using statements including those non erroring (to conserve their order) | ||
$UsingStatementExtents = $AST.FindAll( | ||
{$Args[0] -is [System.Management.Automation.Language.UsingStatementAst]}, | ||
$false | ||
).Extent | ||
|
||
# Edit the Script content by commenting out existing statements (conserving line numbering) | ||
$ScriptText = $AST.Extent.Text | ||
$InsertedCharOffset = 0 | ||
$StatementsToCopy = New-Object System.Collections.ArrayList | ||
foreach ($UsingSatement in $UsingStatementExtents) { | ||
$ScriptText = $ScriptText.Insert($UsingSatement.StartOffset + $InsertedCharOffset, '#') | ||
$InsertedCharOffset++ | ||
|
||
# Keep track of unique statements we'll need to insert at the top | ||
if (!$StatementsToCopy.Contains($UsingSatement.Text)) { | ||
$null = $StatementsToCopy.Add($UsingSatement.Text) | ||
} | ||
} | ||
|
||
$ScriptText = $ScriptText.Insert(0, ($StatementsToCopy -join "`r`n") + "`r`n") | ||
|
||
# Verify we haven't introduced new Parsing errors | ||
$null = [System.Management.Automation.Language.Parser]::ParseInput( | ||
$ScriptText, | ||
[ref]$null, | ||
[ref]$ParseError | ||
) | ||
|
||
if ($ParseError) { | ||
Write-Warning "Oops, it seems that we introduced parsing error(s) while moving the Using Statements. Cancelling changes." | ||
} | ||
else { | ||
$null = Set-Content -Value $ScriptText -Path $RootModule -Encoding $Encoding | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
Describe "MoveUsingStatements" { | ||
|
||
Context "Necessary Parameters" { | ||
$CommandInfo = InModuleScope ModuleBuilder { Get-Command MoveUsingStatements } | ||
|
||
It 'has a mandatory RootModule parameter' { | ||
$RootModule = $CommandInfo.Parameters['RootModule'] | ||
$RootModule | Should -Not -BeNullOrEmpty | ||
$RootModule.Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -be $true | ||
} | ||
|
||
It "has an optional string Encoding parameter" { | ||
$Encoding = $CommandInfo.Parameters['Encoding'] | ||
$Encoding | Should -Not -BeNullOrEmpty | ||
$Encoding.ParameterType | Should -Be ([String]) | ||
$Encoding.Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $False | ||
} | ||
} | ||
|
||
Context "Moving Using Statements to the beginning of the file" { | ||
$MoveUsingStatementsCmd = InModuleScope ModuleBuilder { | ||
Mock Write-Warning {} | ||
Get-Command MoveUsingStatements | ||
} | ||
|
||
$TestCases = @( | ||
@{ | ||
TestCaseName = '2xUsingMustBeAtStartOfScript Fixed' | ||
PSM1File = "function x {`r`n}`r`n" + | ||
"Using namespace System.io`r`n`r`n" + #UsingMustBeAtStartOfScript | ||
"function y {`r`n}`r`n" + | ||
"Using namespace System.Drawing" #UsingMustBeAtStartOfScript | ||
ErrorBefore = 2 | ||
ErrorAfter = 0 | ||
}, | ||
@{ | ||
TestCaseName = 'NoErrors Do Nothing' | ||
PSM1File = "Using namespace System.io`r`n`r`n" + | ||
"Using namespace System.Drawing`r`n" + | ||
"function x { `r`n}`r`n" + | ||
"function y { `r`n}`r`n" | ||
ErrorBefore = 0 | ||
ErrorAfter = 0 | ||
}, | ||
@{ | ||
TestCaseName = 'NotValidPowerShel Do Nothing' | ||
PSM1File = "Using namespace System.io`r`n`r`n" + | ||
"function x { `r`n}`r`n" + | ||
"Using namespace System.Drawing`r`n" + # UsingMustBeAtStartOfScript | ||
"function y { `r`n}`r`n}" # Extra } at the end | ||
ErrorBefore = 2 | ||
ErrorAfter = 2 | ||
} | ||
) | ||
|
||
It 'Should succeed test: "<TestCaseName>" from <ErrorBefore> to <ErrorAfter> parsing errors' -TestCases $TestCases { | ||
param($TestCaseName, $PSM1File, $ErrorBefore, $ErrorAfter) | ||
|
||
$testModuleFile = "$TestDrive\MyModule.psm1" | ||
Set-Content $testModuleFile -value $PSM1File -Encoding UTF8 | ||
# Before | ||
$ErrorFound = $null | ||
$null = [System.Management.Automation.Language.Parser]::ParseFile( | ||
$testModuleFile, | ||
[ref]$null, | ||
[ref]$ErrorFound | ||
) | ||
$ErrorFound.Count | Should -be $ErrorBefore | ||
|
||
# After | ||
&$MoveUsingStatementsCmd -RootModule $testModuleFile | ||
|
||
$null = [System.Management.Automation.Language.Parser]::ParseFile( | ||
$testModuleFile, | ||
[ref]$null, | ||
[ref]$ErrorFound | ||
) | ||
$ErrorFound.Count | Should -be $ErrorAfter | ||
} | ||
} | ||
Context "When MoveUsingStatements should do nothing" { | ||
|
||
$MoveUsingStatementsCmd = InModuleScope ModuleBuilder { | ||
Mock Write-Warning {} | ||
Mock Set-Content {} | ||
Mock Write-Debug {} -ParameterFilter {$Message -eq "No Using Statement Error found." } | ||
|
||
Get-Command MoveUsingStatements | ||
} | ||
|
||
It 'Should Warn and skip when there are Parsing errors other than Using Statements' { | ||
$testModuleFile = "$TestDrive\MyModule.psm1" | ||
$PSM1File = "Using namespace System.IO`r`n function xyz {}`r`n}`r`nUsing namespace System.Drawing" # extra } Set-Content $testModuleFile -value $PSM1File -Encoding UTF8 | ||
Set-Content $testModuleFile -value $PSM1File -Encoding UTF8 | ||
|
||
&$MoveUsingStatementsCmd -RootModule $testModuleFile | ||
Assert-MockCalled -CommandName Write-Warning -Times 1 -ModuleName ModuleBuilder | ||
Assert-MockCalled -CommandName Set-Content -Times 0 -ModuleName ModuleBuilder | ||
} | ||
|
||
It 'Should not do anything when there are no Using Statements Errors' { | ||
|
||
$testModuleFile = "$TestDrive\MyModule.psm1" | ||
$PSM1File = "Using namespace System.IO; function x {}" | ||
Set-Content $testModuleFile -value $PSM1File -Encoding UTF8 | ||
|
||
&$MoveUsingStatementsCmd -RootModule $testModuleFile | ||
|
||
Assert-MockCalled -CommandName Write-Debug -Times 1 -ModuleName ModuleBuilder | ||
Assert-MockCalled -CommandName Set-Content -Times 0 -ModuleName ModuleBuilder | ||
(Get-Content -Raw $testModuleFile).Trim() | Should -Be $PSM1File | ||
|
||
} | ||
|
||
|
||
It 'Should not modify file when introducing parsing errors' { | ||
|
||
$testModuleFile = "$TestDrive\MyModule.psm1" | ||
$PSM1File = "function x {}`r`nUsing namespace System.IO;" | ||
Set-Content $testModuleFile -value $PSM1File -Encoding UTF8 | ||
|
||
InModuleScope ModuleBuilder { | ||
Mock New-Object { | ||
# Introducing Parsing Error in the file | ||
$Flag = [System.Collections.ArrayList]::new() | ||
$null = $Flag.Add("MyParsingError}") | ||
Write-Output -NoEnumerate $Flag | ||
} | ||
} | ||
|
||
&$MoveUsingStatementsCmd -RootModule $testModuleFile | ||
|
||
Assert-MockCalled -CommandName Set-Content -Times 0 -ModuleName ModuleBuilder | ||
Assert-MockCalled -CommandName Write-Warning -Times 1 -ModuleName ModuleBuilder | ||
(Get-Content -Raw $testModuleFile).Trim() | Should -Be $PSM1File | ||
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters