diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 8628dfd44..128d68548 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -1198,8 +1198,12 @@ public Dictionary> GetRuleSuppression(Ast ast) ruleSuppressionList.AddRange(RuleSuppression.GetSuppressions(sbAst.ParamBlock.Attributes, sbAst.Extent.StartOffset, sbAst.Extent.EndOffset, sbAst)); } + // Walk the tree once, retaining the existing function/class/configuration processing order. + var declarationAsts = ast.FindAll(item => item is FunctionDefinitionAst + || item is TypeDefinitionAst || item is ConfigurationDefinitionAst, true).ToArray(); + // Get rule suppression from functions - IEnumerable funcAsts = ast.FindAll(item => item is FunctionDefinitionAst, true).Cast(); + IEnumerable funcAsts = declarationAsts.OfType(); foreach (var funcAst in funcAsts) { @@ -1207,7 +1211,7 @@ public Dictionary> GetRuleSuppression(Ast ast) } // Get rule suppression from classes - IEnumerable typeAsts = ast.FindAll(item => item is TypeDefinitionAst, true).Cast(); + IEnumerable typeAsts = declarationAsts.OfType(); foreach (var typeAst in typeAsts) { @@ -1215,7 +1219,7 @@ public Dictionary> GetRuleSuppression(Ast ast) } // Get rule suppression from configuration definitions - IEnumerable configDefAsts = ast.FindAll(item => item is ConfigurationDefinitionAst, true).Cast(); + IEnumerable configDefAsts = declarationAsts.OfType(); foreach (var configDefAst in configDefAsts) { diff --git a/Tests/Engine/RuleSuppressionDeclaration.tests.ps1 b/Tests/Engine/RuleSuppressionDeclaration.tests.ps1 new file mode 100644 index 000000000..54af59b0b --- /dev/null +++ b/Tests/Engine/RuleSuppressionDeclaration.tests.ps1 @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe 'Suppressions on interleaved declarations' { + It 'suppresses function and class bodies without suppressing a call outside them' { + $definition = @' +function First { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '')] + param() + Invoke-Expression 'first' +} +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '')] +class SuppressedClass { + [void] Emit() { + Invoke-Expression 'class' + } +} +function Last { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '')] + param() + Invoke-Expression 'last' +} +Invoke-Expression 'outside' +'@ + $violations = @(Invoke-ScriptAnalyzer -ScriptDefinition $definition -IncludeRule PSAvoidUsingInvokeExpression) + $violations.Count | Should -Be 1 + $violations[0].Extent.Text | Should -BeExactly "Invoke-Expression 'outside'" + + $suppressed = @(Invoke-ScriptAnalyzer -ScriptDefinition $definition -IncludeRule PSAvoidUsingInvokeExpression -SuppressedOnly) + $suppressed.Count | Should -Be 3 + } +}