From 81e65e0ff85c1c007ccf67df5fec1bec6dcec4b2 Mon Sep 17 00:00:00 2001 From: ztrhgf Date: Wed, 12 May 2021 11:54:11 +0200 Subject: [PATCH] warn about function rename --- repo_content_set_up/.githooks/pre-commit.ps1 | 341 ++++++++++--------- 1 file changed, 187 insertions(+), 154 deletions(-) diff --git a/repo_content_set_up/.githooks/pre-commit.ps1 b/repo_content_set_up/.githooks/pre-commit.ps1 index 300a48c..1d1af3d 100644 --- a/repo_content_set_up/.githooks/pre-commit.ps1 +++ b/repo_content_set_up/.githooks/pre-commit.ps1 @@ -16,48 +16,48 @@ notify user about: $ErrorActionPreference = "stop" # Write-Host is used to display output in GIT console - + #region helper functions function _ErrorAndExit { param ($message) - + if ( !([appdomain]::currentdomain.getassemblies().fullname -like "*System.Windows.Forms*")) { Add-Type -AssemblyName System.Windows.Forms } - + # to GIT console output whole message Write-Host $message - + # in case message is too long, trim $messagePerLine = $message -split "`n" $lineLimit = 40 if ($messagePerLine.count -gt $lineLimit) { $message = (($messagePerLine | select -First $lineLimit) -join "`n") + "`n..." } - + $null = [System.Windows.Forms.MessageBox]::Show($this, $message, 'ERROR', 'ok', 'Error') exit 1 } - + function _WarningAndExit { param ($message) - + if ( !([appdomain]::currentdomain.getassemblies().fullname -like "*System.Windows.Forms*")) { Add-Type -AssemblyName System.Windows.Forms } - + # to GIT console output whole message Write-Host $message - + # in case message is too long, trim $messagePerLine = $message -split "`n" $lineLimit = 40 if ($messagePerLine.count -gt $lineLimit) { $message = (($messagePerLine | select -First $lineLimit) -join "`n") + "`n..." } - + $message = $message + "`n`nAre you sure you want to continue in commit?" - + $msgBoxInput = [System.Windows.Forms.MessageBox]::Show($this, $message, 'Continue?', 'YesNo', 'Warning') switch ($msgBoxInput) { 'No' { @@ -65,7 +65,7 @@ function _WarningAndExit { } } } - + function _GetFileEncoding { [CmdletBinding()] param ( @@ -75,16 +75,16 @@ function _GetFileEncoding { [Parameter(Mandatory = $False)] [System.Text.Encoding] $defaultEncoding = [System.Text.Encoding]::ASCII ) - + process { [Byte[]]$bom = Get-Content -Encoding Byte -ReadCount 4 -TotalCount 4 -Path $path - + $encodingFound = $false - + if ($bom) { foreach ($encoding in [System.Text.Encoding]::GetEncodings().GetEncoding()) { $preamble = $encoding.GetPreamble() - + if ($preamble) { # contains BOM foreach ($i in 0..($preamble.Length - 1)) { @@ -97,15 +97,15 @@ function _GetFileEncoding { } } } - + if (!$encodingFound) { $encodingFound = $defaultEncoding } - + $encodingFound } } - + function _startProcess { [CmdletBinding()] param ( @@ -113,7 +113,7 @@ function _startProcess { [string] $argumentList, [string] $workingDirectory = (Get-Location) ) - + $p = New-Object System.Diagnostics.Process $p.StartInfo.UseShellExecute = $false $p.StartInfo.RedirectStandardOutput = $true @@ -126,18 +126,18 @@ function _startProcess { $p.StandardOutput.ReadToEnd() $p.StandardError.ReadToEnd() } - + function _getAliasAST { param ($AST, $functionName) - + $alias = @() - + # aliases defined by Set-Alias $AST.EndBlock.Statements | ? { $_ -match "^\s*Set-Alias .+" -and $_ -match [regex]::Escape($functionName) } | % { $_.extent.text } | % { $parts = $_ -split "\s+" - + $content += "`n$_" - + if ($_ -match "-na") { # alias set by named parameter # get parameter value @@ -149,42 +149,42 @@ function _getAliasAST { } ++$i } - + $alias += $parts[$parPosition + 1] } else { # alias set by positional parameter $alias += $parts[1] } } - + # aliases defined by [Alias("Some-Alias")] $AST.FindAll( { param([System.Management.Automation.Language.Ast] $AST) - + $AST -is [System.Management.Automation.Language.AttributeAst] }, $true) | ? { $_.parent.extent.text -match '^param' } | Select-Object -ExpandProperty PositionalArguments | Select-Object -ExpandProperty Value -ErrorAction SilentlyContinue | % { $alias += $_ } - + return $alias } - + function _getParameterAST { param ($AST, $functionName) - + $parameter = $AST.FindAll( { $args[0] -is [System.Management.Automation.Language.ParamBlockAst] }, $true) | Where-Object { $_.parent.parent.name -eq $functionName } - + $parameter.parameters | Select-Object @{n = 'name'; e = { $_.name.variablepath.userpath } }, @{n = 'value'; e = { $_.defaultvalue.extent.text } }, @{ n = 'type'; e = { $_.staticType.name } } } - + function _getVariableAST { param ($AST, $varToExclude, [switch] $raw) - + $variable = $AST.FindAll( { $args[0] -is [System.Management.Automation.Language.VariableExpressionAst ] }, $true) $variable = $variable | Where-Object { $_.parent.left -or $_.parent.type -and ($_.parent.operator -eq 'Equals' -or $_.parent.parent.operator -eq 'Equals') } - + if ($raw) { return $variable } - + $variable = $variable | Select-Object @{n = "name"; e = { $_.variablepath.userPath } }, @{n = "value"; e = { if ($value = $_.parent.right.extent.text) { $value @@ -194,23 +194,23 @@ function _getVariableAST { } } } - + # because of later comparison unify newline symbol (CRLF vs LF) $variable = $variable | Select-Object name, @{n = "value"; e = { $_.value.Replace("`r`n", "`n") } } - + if ($varToExclude) { $variable = $variable | Where-Object { $_.name -notmatch $varToExclude } } - + return $variable } - + function _getFunctionAST { param ($AST) - + $AST.FindAll( { param([System.Management.Automation.Language.Ast] $AST) - + $AST -is [System.Management.Automation.Language.FunctionDefinitionAst] -and # Class methods have a FunctionDefinitionAst under them as well, but we don't want them. ($PSVersionTable.PSVersion.Major -lt 5 -or @@ -218,14 +218,14 @@ function _getFunctionAST { }, $false) } #endregion helper functions - + try { # switch to repository root Set-Location $PSScriptRoot Set-Location .. $rootFolder = Get-Location $rootFolderName = ((Get-Location) -split "\\")[-1] - + try { $repoStatus = git.exe status -uno # files to commit @@ -246,6 +246,23 @@ try { $commitedDeletedFile = @(git.exe diff --name-status --cached --diff-filter=D | ForEach-Object { $_ -replace "^D\s+" }) # deleted commited ps1 scripts which can contain functions, that could have been used somewhere $commitedDeletedPs1 = @($commitedDeletedFile | Where-Object { $_ -match "\.ps1$" } | Where-Object { $_ -match "scripts2module/|scripts2root/profile\.ps1" }) + # renamed commited files + $commitedRenamedFileHash = @{} + # renamed commited ps1 scripts which can contain functions, that could have been used somewhere + $commitedRenamedPs1Hash = @{} + $filesToCommitStatus | % { + $capture = ([regex]'^\s*R\s+"?([^" ]+)"? -> "?([^`" ]+)"?').Matches($_).captures + if ($capture) { + $original = $capture.groups[1].Value -replace '"' + $renamed = $capture.groups[2].Value -replace '"' + $commitedRenamedFileHash.$original = $renamed + if ($original -match "\.ps1$") { + $commitedRenamedPs1Hash.$original = $renamed + } + } + } + # path to commited renamed ps1 + $commitedRenamedPs1 = $commitedRenamedPs1Hash.Keys } catch { $err = $_ if ($err -match "is not recognized as the name of a cmdlet") { @@ -254,8 +271,8 @@ try { _ErrorAndExit $err } } - - + + # # check that repository contains recent data # it's not possible to call git pull automatically because it ends with error 'fatal: cannot lock ref 'HEAD': is at cfd4a815a.. but expected 37936..' @@ -263,8 +280,8 @@ try { if ($repoStatus -match "Your branch is behind") { _ErrorAndExit "Repository doesn't contain actual data. Pull them (git pull or sync icon in VSC) and try again." } - - + + # # check that commited file wasn't modified after adding to commit # it makes working with repository data a lot easier (checks, obtaining previsou file version etc) @@ -276,24 +293,24 @@ try { } } } - - + + # # throw error in case that commit deletes important files "- exit if commit deletes important files" if ($commitedDeletedFile | Where-Object { $_ -match "custom/customConfig\.ps1" }) { _ErrorAndExit "You are deleting customConfig, which is needed for Custom section to work. On 99,99% you don't want do this!" } - + if ($commitedDeletedFile | Where-Object { $_ -match "scripts2root/PS_env_set_up\.ps1" }) { _ErrorAndExit "You are deleting PS_env_set_up, which is needed for deploy of repository content to clients. On 99,99% you don't want do this!" } - + if ($commitedDeletedFile | Where-Object { $_ -match "modules/Variables/Variables\.psm1" }) { _ErrorAndExit "You are deleting module Variables, which contains important variables like _repoSyncServer, _computerWithProfile etc. On 99,99% you don't want do this!" } - - + + # # checks that commit doesn't contain module, which is in the same time auto-generated from scripts2module # one of them would be replaced in DFS share @@ -303,13 +320,13 @@ try { $module2commit = ($module2commit -split "/")[1] # names of modules that are auto-generated $generatedModule = Get-ChildItem "scripts2module" -Directory -Name - + if ($conflictedModule = $module2commit | Where-Object { $_ -in $generatedModule }) { _ErrorAndExit "It's not possible to commit module ($($conflictedModule -join ', ')), which is in the same time generated from content of scripts2module." } } - - + + # # checks of variable $customConfig from customConfig.ps1 # using AST instead of dot sourcing the file to avoid errors in case, that this script runs on non-domain joined computer and $customConfig contains dynamic variable that contains Active Directory data @@ -322,63 +339,63 @@ try { if (!$configVar) { _ErrorAndExit "customConfig.ps1 is not defining variable `$customConfig. It has to, at least empty one." } - + # save right side of $customConfig ie array of objects $configValueItem = $configVar.parent.right.expression.subexpression.statements.pipelineelements.expression.elements if (!$configValueItem) { # on right side is just one item $configValueItem = $configVar.parent.right.expression.subexpression.statements.pipelineelements.expression } - + # check that value contains just psobject types if ($configValueItem | ? { $_.type.typename.name -ne "PSCustomObject" }) { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig has to contain array of PSCustomObject items, which it hasn't." } - + # folders that are set in $customConfig $folderNames = @() - + $configValueItem | % { $item = $_ $folderName = ($item.child.keyvaluepairs | ? { $_.item1.value -eq "folderName" }).item2.pipelineelements.extent.text -replace '"' -replace "'" - + # check that folderName don't contains subfolder #TODO dodelat podporu, aby to slo if ($folderName -match "\\") { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig defines folderName '$folderName'. FolderName can't contain \ in it's name." } - + $item.child.keyvaluepairs | % { $key = $_.item1.value $value = $_.item2.pipelineelements.extent.text -replace '"' -replace "'" - + # check that only valid keys are used $validKey = "computerName", "folderName", "customDestinationNTFS", "customSourceNTFS", "customLocalDestination", "customShareDestination", "copyJustContent", "scheduledTask" if ($key -and ($nonvalidKey = Compare-Object $key $validKey | ? { $_.sideIndicator -match "<=" } | Select-Object -ExpandProperty inputObject)) { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig contains unnaproved keys: ($($nonvalidKey -join ', ')). Approved are only: $($validKey -join ', ')" } - + # check that folderName, customLocalDestination, customShareDestination contains maximum of one value if ($key -in ("folderName", "customLocalDestination", "customShareDestination") -and ($value -split ',').count -ne 1) { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig contains in object that defines '$folderName' in key $key more than one values. Values in key are: $($value -join ', ')" } - + # check that customShareDestination is in UNC format if ($key -match "customShareDestination" -and $value -notmatch "^\\\\") { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig doesn't contain in object that defines '$folderName' in key $key UNC path. Value of key is '$value'" } - + # check that customLocalDestination is local path format # regular expression is this basic on purpose, to enable use of variables if ($key -match "customLocalDestination" -and $value -match "^\\\\") { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig doesn't contain in object that defines '$folderName' in key $key local path. Value of key is '$value'" } - + # check that scheduled task defined in scheduledTask key have corresponding XML definition file in root of appropriate Custom folder if ($key -match "scheduledTask" -and $value) { ($value -split ",").trim() | % { $taskName = $_ - + $unixPath = 'custom/{0}/{1}.xml' -f ($folderName -replace "\\", "/"), $taskName $alreadyInRepo = _startProcess git "show `"HEAD:$unixPath`"" if ($alreadyInRepo -match "^fatal: ") { @@ -390,14 +407,14 @@ try { if (!$alreadyInRepo -and !$inActualCommit) { _ErrorAndExit "In customConfig.ps1 object that defines '$folderName' in key $key, defines scheduled task '$taskName', but associated config file $windowsPath is neither in remote GIT repository\Custom\$folderName nor in actual commit (name is case sensitive!). It would cause error on clients." } - + # check (very basic) that XML really contains scheduled task definition $XMLPath = Join-Path $rootFolder "Custom\$folderName\$taskName.xml" [xml]$xmlDefinition = Get-Content $XMLPath if (!$xmlDefinition.Task.RegistrationInfo) { _ErrorAndExit "In customConfig.ps1 object that defines '$folderName' in key $key, defines scheduled task '$taskName', but associated config file $windowsPath doesn't contain valid data (RegistrationInfo tag is missing). This would cause error on clients, fix it." } - + # warn if scheduled task name defined in XML file differs from name in CustomConfig # won't work on scheduled task definition created on Windows Server 2012, because it doesn't contain URI tag $taskNameInXML = $xmlDefinition.task.RegistrationInfo.URI -replace "^\\" @@ -407,21 +424,21 @@ try { } } } - + # list of all object keys $keys = $item.child.keyvaluepairs.item1.value # throw an error in case that mandatory key folderName is missing if ($keys -notcontains "folderName") { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig doesn't contain mandatory key folderName at some of objects." } - + $folderNames += $folderName - + # warn about potential conflict in NTFS rights that should be set if ($keys -contains "computerName" -and $keys -contains "customSourceNTFS") { _WarningAndExit "In customConfig.ps1 script variable `$customConfig contains in object that defines '$folderName' keys: computerName, customSourceNTFS at the same time. This is safe only in case, when customSourceNTFS contains all computers from computerName (and more)." } - + # check that just supported keys are used together if ($keys -contains "copyJustContent" -and $keys -contains "computerName" -and $keys -notcontains "customLocalDestination") { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig contains in object that defines '$folderName' copyJustContent and computerName, but no customLocalDestination. To destination folder (Scripts) are always copied whole folders." @@ -430,7 +447,7 @@ try { # when copy to default destinationn (Windows\Scripts) copyJustContent is ignored, so rights in customDestinationNTFS will be set _ErrorAndExit "In customConfig.ps1 script variable `$customConfig contains in object that defines '$folderName' customDestinationNTFS, but that's not possible, because copyJustContent is also used and therefore NTFS rights are not configuring." } - + # check that value in folderName is existing folder in Custom directory # in actual commit or in cloud GIT repository $unixFolderPath = 'custom/{0}' -f ($folderName -replace "\\", "/") @@ -445,11 +462,11 @@ try { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig contains object that defines '$folderName', but given folder is neither in remote GIT repository\Custom nor in actual commit (name is case sensitive!). It would cause error on clients." } } - + if ($folderNames -notcontains "Repo_sync") { _ErrorAndExit "In customConfig.ps1 script variable `$customConfig has to contain PSCustomObject that defines Repo_sync. It is necesarry for transfer data from MGM server to DFS share works." } - + # warn about folders that are defined multiple times $ht = @{ } $folderNames | % { $ht["$_"] += 1 } @@ -463,9 +480,9 @@ try { } } #endregion - - - + + + # # checks of variable $modulesConfig from modulesConfig.ps1 # using AST instead of dot sourcing the file to avoid errors in case, that this script runs on non-domain joined computer and $modulesConfig contains dynamic variable that contains Active Directory data @@ -478,56 +495,56 @@ try { if (!$configVar) { _ErrorAndExit "modulesConfig.ps1 doesn't define variable `$modulesConfig." } - + # save right side of $modulesConfig ie array of objects $configValueItem = $configVar.parent.right.expression.subexpression.statements.pipelineelements.expression.elements if (!$configValueItem) { # on right side is just one item $configValueItem = $configVar.parent.right.expression.subexpression.statements.pipelineelements.expression } - + if ($configValueItem) { # check that value contains just psobject types if ($configValueItem | ? { $_.type.typename.name -ne "PSCustomObject" }) { _ErrorAndExit "In modulesConfig.ps1 script variable `$modulesConfig has to contain array of PSCustomObject items." } - + # folders that are set in $modulesConfig $folderNames = @() - + $configValueItem | % { $item = $_ $folderName = ($item.child.keyvaluepairs | ? { $_.item1.value -eq "folderName" }).item2.pipelineelements.extent.text -replace '"' -replace "'" - + # check that folderName don't contains subfolder if ($folderName -match "\\") { _ErrorAndExit "In modulesConfig.ps1 script variable `$modulesConfig key folderName '$folderName' contains '\', but that's not allowed." } - + $item.child.keyvaluepairs | % { $key = $_.item1.value $value = $_.item2.pipelineelements.extent.text -replace '"' -replace "'" - + # check that only valid keys are used $validKey = "computerName", "folderName" if ($key -and ($nonvalidKey = Compare-Object $key $validKey | ? { $_.sideIndicator -match "<=" } | Select-Object -ExpandProperty inputObject)) { _ErrorAndExit "In modulesConfig.ps1 script variable `$modulesConfig contains unnaproved keys ($($nonvalidKey -join ', ')). Approved are just: $($validKey -join ', ')" } - + # check that folderName contains maximum of one value if ($key -in ("folderName") -and ($value -split ',').count -ne 1) { _ErrorAndExit "In modulesConfig.ps1 script variable `$modulesConfig contains in object that defines '$folderName' in key $key more than one value. Value of key is $($value -join ', ')" } } - + $keys = $item.child.keyvaluepairs.item1.value # throw an error in case that mandatory key folderName is missing if ($keys -notcontains "folderName") { _ErrorAndExit "In modulesConfig.ps1 script variable `$modulesConfig doesn't contain mandatory key folderName at some object." } - + $folderNames += $folderName - + # check that value in folderName is existing folder in Modules or scripts2module directory # in actual commit or in cloud GIT repository $unixFolderPath = ('modules/{0}' -f ($folderName -replace "\\", "/")), ('scripts2module/{0}' -f ($folderName -replace "\\", "/")) @@ -541,18 +558,18 @@ try { $folderAlreadyInRepo = '' } } - + $windowsFolderPath = $_ -replace "/", "\" if (!$folderInActualCommit) { $folderInActualCommit = $filesToCommitNoDEL | Where-Object { $_ -cmatch [regex]::Escape($windowsFolderPath) } } } - + if (!$folderAlreadyInRepo -and !$folderInActualCommit) { _WarningAndExit "In modulesConfig.ps1 script variable `$modulesConfig contains object that defines '$folderName', but given folder is neither in GIT repository\Modules or repository\scripts2module nor in actual commit (name is case sensitive!)." } } - + # warn about folders that are defined multiple times $ht = @{ } $folderNames | % { $ht["$_"] += 1 } @@ -567,7 +584,7 @@ try { #endregion - + # # check that commit doesn't contain any files in root of modules folder # such files won't be processed anyway @@ -586,8 +603,8 @@ try { if ($scripts2moduleRootFile) { _ErrorAndExit "File(s) $($scripts2moduleRootFile -join ', ') can't be in root of 'scripts2module' folder. To generate module, save the ps1 file containing the same named function as scripts2module\\.ps1." } - - + + # # check commited script files encoding "- check encoding ..." @@ -601,7 +618,7 @@ try { } } } - + # @@ -614,13 +631,13 @@ try { } catch { _WarningAndExit "Module PSScriptAnalyzer isn't available (respective command Invoke-ScriptAnalyzer). It's not possible to check syntax of ps1 scripts." } - + $ps1Error = @() $ps1CompatWarning = @() - + $psFilesToCommit | ForEach-Object { $script = $_ - + # # check that script doesn't contain non ASCII chars that would break parser (ie EN DASH or EM DASH instead of dash etc) # such chars in combination with UTF8 cause various parse errors @@ -631,7 +648,7 @@ try { $problematicLine = $problematicLine.Trim() _ErrorAndExit "File $([System.IO.Path]::GetFileName($script)) contains problematic character (en dash instead of dash?).`nOn row:`n`n$($problematicLine -join "`n`n")" } - + # # check syntax errors and best practices compliance Invoke-ScriptAnalyzer $script -Settings .\PSScriptAnalyzerSettings.psd1 -Verbose | % { @@ -641,28 +658,28 @@ try { $ps1Error += $_ } } - - + + # # warn if script contains FIXME comment # cross sign by its [char] reprezentation so script dont warns about itself if ($fixme = Get-Content $script | ? { $_ -match ("\s*" + [char]0x023 + "\s*" + "FIXME\b") }) { _WarningAndExit "File $script contains FIXME:`n$($fixme.trim() -join "`n")." } - + # - # check of scripts that are used to generate modules + # check scripts that are used to generate modules if ($script -match "\\$rootFolderName\\scripts2module\\") { $ast = [System.Management.Automation.Language.Parser]::ParseFile("$script", [ref] $null, [ref] $null) - + $wrgMessage = "File $script is not in correct format. It has to contain just definition of one function (with the same name). Beside that, script can also contains: Set-Alias, comments or requires statement!" - + # # check that just END block exists if ($ast.BeginBlock -or $ast.ProcessBlock) { _ErrorAndExit $wrgMessage } - + # # check that script doesn't contain code, that would be skipped anyway when module generation occurs $ast.EndBlock.Statements | ForEach-Object { @@ -670,16 +687,16 @@ try { _ErrorAndExit $wrgMessage } } - + # save function that this script defines $functionDefinition = _getFunctionAST $AST - + # # check that just one function is defined if ($functionDefinition.count -ne 1) { _ErrorAndExit "File $script either doesn't contain any function definition or contain more than one." } - + # # check that ps1 script is named same as the function it defines $fileName = [System.IO.Path]::GetFileNameWithoutExtension($script) @@ -687,13 +704,13 @@ try { if ($fileName -ne $functionName) { _ErrorAndExit "File $script has to be named exactly the same as function that it defines ($functionName)." } - + # # warn about functions whose parameters or aliases were changed in case such function is used somewhere in repository $actParameter = _getParameterAST $AST $functionName - + $actAlias = _getAliasAST $AST $functionName - + # AST is used to get all parameters that function had in previous version # absolute path to script is converted to relative with unix slashes $scriptUnixPath = $script -replace ([regex]::Escape((Get-Location))) -replace "\\", "/" -replace "^/" @@ -703,22 +720,22 @@ try { Write-Warning "Previous version of $scriptUnixPath cannot be found (to check modified parameters/aliases)." } else { $gitAST = [System.Management.Automation.Language.Parser]::ParseInput(($lastCommitContent -join "`n"), [ref]$null, [ref]$null) - + $prevParameter = _getParameterAST $gitAST $functionName - + $prevAlias = _getAliasAST $gitAST $functionName } - + if ($actParameter -and $prevParameter -and ($changedParam = Compare-Object $actParameter $prevParameter -Property name, value, type | ? { $_.sideIndicator -eq "=>" } | select -exp name)) { $escFuncName = [regex]::Escape($functionName) # get all files where changed function is mentioned (even in comments) $fileUsed = git.exe grep --ignore-case -l "\b$escFuncName\b" # exclude scripts where this function is defined $fileUsed = $fileUsed | Where-Object { $_ -notmatch "/$escFuncName\.ps1" } - + if ($fileUsed) { $fileUsed = $fileUsed -replace "/", "\" - + _WarningAndExit "Function $functionName which parameters has changed ($($changedParam -join ', ')) is mentioned in following scripts:`n$($fileUsed -join "`n")" } } @@ -737,17 +754,17 @@ try { $fileUsed = git.exe grep --ignore-case -l "\b$escAlias\b" # exclude scripts where this alias is defined $fileUsed = $fileUsed | Where-Object { $_ -notmatch "/$escFuncName\.ps1" } - + if ($fileUsed) { $fileUsed = $fileUsed -replace "/", "\" - + _WarningAndExit "Alias '$alias' of function $functionName was deleted, but is still used in following scripts:`n$($fileUsed -join "`n")" } } } } } - + if ($ps1Error) { # ps1 scripts in commit has errors if (!($ps1Error | Where-Object { $_.ruleName -ne "PSAvoidUsingConvertToSecureStringWithPlainText" })) { @@ -757,20 +774,20 @@ try { } else { # ps1 scripts contain severe errors $ps1Error = $ps1Error | Select-Object Scriptname, Line, Column, Message | Format-List | Out-String -Width 1200 - _ErrorAndExit "Following serious misdemeanors agains best practices were found:`n$ps1Error`n`nFix and commit again." + _WarningAndExit "Following SERIOUS misdemeanors agains best practices were found:`n$ps1Error`n`nFix and commit again." } } - + if ($ps1CompatWarning) { # ps1 scripts in commit contain commands that are not compatible with Powershell version set in .vscode\PSScriptAnalyzerSettings.psd1 $ps1CompatWarning = $ps1CompatWarning | Select-Object Scriptname, Line, Column, Message | Format-List | Out-String -Width 1200 _WarningAndExit "Compatibility issues were found:`n$ps1CompatWarning" } } # end of ps1 and psm1 checks - - + + # - # warn about ps1 script that define function, in case it is used somewhere in repository + # warn about deleted ps1 script (that defines function for module generation), in case it is used somewhere in repository if ($commitedDeletedPs1) { $commitedDeletedPs1 = $commitedDeletedPs1 -replace "/", "\" $commitedDeletedPs1 | Where-Object { $_ -match "scripts2module\\" } | ForEach-Object { @@ -782,47 +799,63 @@ try { $fileFuncUsed = git.exe grep --ignore-case -l "\b$escFuncName\b" if ($fileFuncUsed) { $fileFuncUsed = $fileFuncUsed -replace "/", "\" - + _WarningAndExit "Deleted function $funcName is mentioned in following scripts:`n$($fileFuncUsed -join "`n")" } } #TODO kontrola funkci v profile.ps1? viz AST sekce https://devblogs.microsoft.com/scripting/learn-how-it-pros-can-use-the-powershell-ast/ } - - + + # + # warn about renamed ps1 script (that defines function for module generation), in case it is used somewhere in repository + if ($commitedRenamedPs1) { + $commitedRenamedPs1 = $commitedRenamedPs1 -replace "/", "\" + $commitedRenamedPs1 | Where-Object { $_ -match "scripts2module\\" } | ForEach-Object { + $funcName = [System.IO.Path]::GetFileNameWithoutExtension($_) + $escFuncName = [regex]::Escape($funcName) + $fileFuncUsed = git.exe grep --ignore-case -l "\b$escFuncName\b" + if ($fileFuncUsed) { + $fileFuncUsed = $fileFuncUsed -replace "/", "\" + + _WarningAndExit "Renamed function $funcName is mentioned in following scripts:`n$($fileFuncUsed -join "`n")" + } + } + } + + # # checks of module Variables if ([string]$variablesModule = $filesToCommit -match "Variables\.psm1") { "- check module Variables ..." - + # # get all variables defined in module using AST $varToExclude = 'env:|ErrorActionPreference|WarningPreference|VerbosePreference|^\$_$' $variablesModuleUnixPath = $variablesModule $variablesModule = Join-Path $rootFolder $variablesModule - + $AST = [System.Management.Automation.Language.Parser]::ParseFile($variablesModule, [ref]$null, [ref]$null) $actVariables = _getVariableAST $AST $varToExclude - + # get all variables defined in previous module version using AST $lastCommitContent = _startProcess git "show HEAD:$variablesModuleUnixPath" if (!$lastCommitContent -or $lastCommitContent -match "^fatal: ") { Write-Warning "Previous version of module Variables cannot be found (to check changed variables)." } else { $gitAST = [System.Management.Automation.Language.Parser]::ParseInput(($lastCommitContent -join "`n"), [ref]$null, [ref]$null) - + $prevVariables = _getVariableAST $gitAST $varToExclude } - - + + # # check that module doesn't define one variable multiple times $duplicateVariable = $actVariables | Group-Object name | Where-Object { $_.count -gt 1 } | Select-Object -ExpandProperty name if ($duplicateVariable) { _WarningAndExit "In module Variables are following variables defined more than once: $($duplicateVariable -join ', ')" } - - + + # # warn about deleted variables that are used somewhere in repository $removedVariable = $prevVariables.name | Where-Object { $_ -notin $actVariables.name } @@ -836,28 +869,28 @@ try { $fileUsed = $fileUsed | Where-Object { $_ -notmatch "/Variables\.psm1" } if ($fileUsed) { $fileUsed = $fileUsed -replace "/", "\" - + _WarningAndExit "Deleted variable $varName is mentioned in following scripts:`n$($fileUsed -join "`n")" } } } - + $newVariable = $actVariables.name | Where-Object { $_ -notin $prevVariables.name } - + # # warn about new variables that do no start with underscore $optimalVariableNameRegex = "^_" if ($badNewVariables = $newVariable | ? { $_ -notmatch $optimalVariableNameRegex }) { $badNewVariables = $badNewVariables | % { "'$_'" } if ($badNewVariables.count -gt 1) { $s = "s" } - _WarningAndExit "New variable$s`: $($badNewVariables -join ', ') doesn't start with '_', which is highly recommended to distinct them from variables defined in scripts or functions themselves." + _WarningAndExit "New variable$s`: $($badNewVariables -join ', ') doesn't start with '_', which is highly recommended to distinct it from variables defined in scripts or functions themselves." } - + # # throw an error in case new variable could replace some variable in repo critical files if ($newVariable) { $repoFileImportingVariablesModule = (Join-Path $rootFolder "Custom\Repo_sync\repo_sync.ps1"), (Join-Path $rootFolder "scripts2root\PS_env_set_up.ps1") - + $repoFileImportingVariablesModule | % { $repoFile = $_ $AST = [System.Management.Automation.Language.Parser]::ParseFile($repoFile, [ref]$null, [ref]$null) @@ -869,7 +902,7 @@ try { } } } - + # # warn about modified variables in case they are used somewhere in repository # to be able to use Compare-Object, just variables that can be compared are left @@ -881,16 +914,16 @@ try { $fileUsed = git.exe grep --ignore-case -l "$escVarName\b" # filter module Variable $fileUsed = $fileUsed | Where-Object { $_ -notmatch "/Variables\.psm1" } - + if ($fileUsed) { $fileUsed = $fileUsed -replace "/", "\" - + _WarningAndExit "Modified variable $varName is mentioned in following scripts:`n$($fileUsed -join "`n")" } } } - - + + # # throw an error in case module Variables doesn't contain necessarry command for export of variables $AST = [System.Management.Automation.Language.Parser]::ParseFile($variablesModule, [ref]$null, [ref]$null) @@ -899,8 +932,8 @@ try { _ErrorAndExit "Module Variables doesn't export any variables using Export-ModuleMember.`n`nFix and commit again." } } # end of module Variable checks - - + + # # again check that data in repository are recent # it is possible that somebody else could pushed new commit when checks were running @@ -919,5 +952,5 @@ try { _ErrorAndExit "There was an error:`n$err`n`nFix and commit again." } } - + "DONE" \ No newline at end of file