From 53fb6589516d20e7466276db65ed7e8709194ad5 Mon Sep 17 00:00:00 2001 From: Benjamin Fuchs Date: Sat, 21 Dec 2024 16:12:17 +0100 Subject: [PATCH] Add support for excluding functions with [ExcludeFromCodeCoverageAttribute()] attribute --- src/functions/Coverage.ps1 | 68 ++++++++++++++- tst/functions/Coverage.Tests.ps1 | 144 +++++++++++++++++-------------- 2 files changed, 144 insertions(+), 68 deletions(-) diff --git a/src/functions/Coverage.ps1 b/src/functions/Coverage.ps1 index 781b9c331..f19cd1c78 100644 --- a/src/functions/Coverage.ps1 +++ b/src/functions/Coverage.ps1 @@ -292,6 +292,12 @@ function Get-CommandsInFile { # "return" is not hit in 5.1 but fixed in a later version. Using "return 123" we get hit on 123 but not return. # See https://github.com/pester/Pester/issues/1465#issuecomment-604323645 $predicate = { + # Check if this node is within a function with the exclude attribute + if (IsExcludedByAttribute -Ast $args[0]) { + return $false + } + + # Include relevant AST types for code coverage $args[0] -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -or $args[0] -is [System.Management.Automation.Language.CommandBaseAst] -or $args[0] -is [System.Management.Automation.Language.BreakStatementAst] -or @@ -301,13 +307,73 @@ function Get-CommandsInFile { } } else { - $predicate = { $args[0] -is [System.Management.Automation.Language.CommandBaseAst] } + $predicate = { + # Check if this node is within a function with the exclude attribute + if (IsExcludedByAttribute -Ast $args[0]) { + return $false + } + + $args[0] -is [System.Management.Automation.Language.CommandBaseAst] + } } $searchNestedScriptBlocks = $true $ast.FindAll($predicate, $searchNestedScriptBlocks) } +function IsExcludedByAttribute { + param ( + [System.Management.Automation.Language.Ast] $Ast + ) + + for ($parent = $Ast.Parent; $null -ne $parent; $parent = $parent.Parent) { + if ($parent -is [System.Management.Automation.Language.FunctionDefinitionAst]) { + $paramBlock = $parent.Body.ParamBlock + if ($paramBlock -and $paramBlock.Attributes) { + foreach ($attribute in $paramBlock.Attributes) { + $attributeName = $attribute.TypeName.FullName + + if ($attributeName.EndsWith('ExcludeFromCodeCoverageAttribute')) { + if ($attributeName -eq 'System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute') { + return $true + } + else { + $namespaces = Get-NamespacesFromScript -Ast $Ast + if ($namespaces -and ($namespaces | Where-Object { "$_.$attributeName" -eq 'System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute' })) { + return $true + } + } + } + } + } + } + } + + return $false +} + +function Get-NamespacesFromScript { + param ( + [System.Management.Automation.Language.Ast] $Ast + ) + + while ($Ast.Parent -ne $null) { + $Ast = $Ast.Parent + } + + $usingNamespaces = @() + + $Ast.FindAll({ + param ($node) + $node -is [System.Management.Automation.Language.UsingStatementAst] -and + $node.UsingStatementKind -eq 'Namespace' + }, $true) | ForEach-Object { + $usingNamespaces += $_.Name.Value + } + + return $usingNamespaces +} + function Test-CoverageOverlapsCommand { param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command) diff --git a/tst/functions/Coverage.Tests.ps1 b/tst/functions/Coverage.Tests.ps1 index b108745fb..d801f4463 100644 --- a/tst/functions/Coverage.Tests.ps1 +++ b/tst/functions/Coverage.Tests.ps1 @@ -23,6 +23,8 @@ InPesterModuleScope { $null = New-Item -Path $testScriptPath -ItemType File -ErrorAction SilentlyContinue Set-Content -Path $testScriptPath -Value @' + using namespace System.Diagnostics.CodeAnalysis + function FunctionOne { function NestedFunction @@ -47,6 +49,14 @@ InPesterModuleScope { 'I am function two. I never get called.' } + function FunctionThree + { + [ExcludeFromCodeCoverageAttribute(Justification = 'I am not covered')] + param () + + 'I am function three. I never get called.' + } + FunctionOne '@ @@ -288,42 +298,42 @@ InPesterModuleScope { - + - + - + - + - + - + - + - + @@ -367,21 +377,21 @@ InPesterModuleScope { - - - + + - - - - - - - - - - + + + + + + + + + + + @@ -480,42 +490,42 @@ InPesterModuleScope { - + - + - + - + - + - + - + - + @@ -559,21 +569,21 @@ InPesterModuleScope { - - - + + - - - - - - - - - - + + + + + + + + + + + @@ -675,61 +685,61 @@ InPesterModuleScope { - - - - + + + + - + - + - + - + - + - - + + - - - + + - - - - - - - - - - + + + + + + + + + + +