Skip to content

Commit 4bc3911

Browse files
mattmcnabbbergmeister
authored andcommitted
Add rule for missing process block when command supports the pipeline (#1373)
* Add rule for missing process block #1368 * Fix build by regenerating strongly typed files by running '.\New-StronglyTypedCsFileForResx.ps1 Rules' * Update strongly typed resources via Visual Studio * minimise diff in resx file * refactor UseProcessBlockForPipelineCommands rule * add tests and docs for UseProcessBlockForPipelineCommands rule * Fix logic non-pipeline function and add test case * fixing some test failures * incrememt number of expected rules * fix style suggestions for PR #1373 * fix rule readme.md * clean diff for Strings.resx to avoid future merge conflicts * Fix last 2 rule documentation test failures by fixing markdown link * Update Rules/Strings.resx Co-Authored-By: Christoph Bergmeister [MVP] <c.bergmeister@gmail.com> * Update Rules/Strings.resx Co-Authored-By: Christoph Bergmeister [MVP] <c.bergmeister@gmail.com> * Update Rules/Strings.resx Co-Authored-By: Christoph Bergmeister [MVP] <c.bergmeister@gmail.com> * empty-commit to re-trigger CI due to sporadic failure * Update Rules/Strings.resx Co-Authored-By: Robert Holt <rjmholt@gmail.com> * Update Rules/Strings.resx Co-Authored-By: Robert Holt <rjmholt@gmail.com> * corrections to use process block rule and tests - candidate for merge * More style fixes for #1373 * update localized strings * replace implicitly typed vars
1 parent 20c4611 commit 4bc3911

7 files changed

+304
-57
lines changed

RuleDocumentation/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
|[UseDeclaredVarsMoreThanAssignments](./UseDeclaredVarsMoreThanAssignments.md) | Warning | |
5151
|[UseLiteralInitializerForHashtable](./UseLiteralInitializerForHashtable.md) | Warning | |
5252
|[UseOutputTypeCorrectly](./UseOutputTypeCorrectly.md) | Information | |
53+
|[UseProcessBlockForPipelineCommand](./UseProcessBlockForPipelineCommand.md) | Warning | |
5354
|[UsePSCredentialType](./UsePSCredentialType.md) | Warning | |
5455
|[UseShouldProcessForStateChangingFunctions](./UseShouldProcessForStateChangingFunctions.md) | Warning | |
5556
|[UseSingularNouns<sup>*</sup>](./UseSingularNouns.md) | Warning | |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# UseProcessBlockForPipelineCommand
2+
3+
**Severity Level: Warning**
4+
5+
## Description
6+
7+
Functions that support pipeline input should always handle parameter input in a process block. Unexpected behavior can result if input is handled directly in the body of a function where parameters declare pipeline support.
8+
9+
## Example
10+
11+
### Wrong
12+
13+
``` PowerShell
14+
Function Get-Number
15+
{
16+
[CmdletBinding()]
17+
Param(
18+
[Parameter(ValueFromPipeline)]
19+
[int]
20+
$Number
21+
)
22+
23+
$Number
24+
}
25+
```
26+
27+
#### Result
28+
29+
```
30+
PS C:\> 1..5 | Get-Number
31+
5
32+
```
33+
34+
### Correct
35+
36+
``` PowerShell
37+
Function Get-Number
38+
{
39+
[CmdletBinding()]
40+
Param(
41+
[Parameter(ValueFromPipeline)]
42+
[int]
43+
$Number
44+
)
45+
46+
process
47+
{
48+
$Number
49+
}
50+
}
51+
```
52+
53+
#### Result
54+
55+
```
56+
PS C:\> 1..5 | Get-Number
57+
1
58+
2
59+
3
60+
4
61+
5
62+
```

Rules/Strings.Designer.cs

+73-56
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rules/Strings.resx

+12
Original file line numberDiff line numberDiff line change
@@ -1080,4 +1080,16 @@
10801080
<data name="UseCorrectCasingName" xml:space="preserve">
10811081
<value>UseCorrectCasing</value>
10821082
</data>
1083+
<data name="UseProcessBlockForPipelineCommandCommonName" xml:space="preserve">
1084+
<value>Use process block for command that accepts input from pipeline.</value>
1085+
</data>
1086+
<data name="UseProcessBlockForPipelineCommandDescription" xml:space="preserve">
1087+
<value>If a command parameter takes its value from the pipeline, the command must use a process block to bind the input objects from the pipeline to that parameter.</value>
1088+
</data>
1089+
<data name="UseProcessBlockForPipelineCommandError" xml:space="preserve">
1090+
<value>Command accepts pipeline input but has not defined a process block.</value>
1091+
</data>
1092+
<data name="UseProcessBlockForPipelineCommandName" xml:space="preserve">
1093+
<value>UseProcessBlockForPipelineCommand</value>
1094+
</data>
10831095
</root>
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Management.Automation.Language;
7+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
8+
#if !CORECLR
9+
using System.ComponentModel.Composition;
10+
#endif
11+
using System.Globalization;
12+
13+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
14+
{
15+
#if !CORECLR
16+
[Export(typeof(IScriptRule))]
17+
#endif
18+
public class UseProcessBlockForPipelineCommand : IScriptRule
19+
{
20+
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
21+
{
22+
if (ast == null)
23+
{
24+
throw new ArgumentNullException(Strings.NullAstErrorMessage);
25+
}
26+
27+
IEnumerable<Ast> scriptblockAsts = ast.FindAll(testAst => testAst is ScriptBlockAst, true);
28+
29+
foreach (ScriptBlockAst scriptblockAst in scriptblockAsts)
30+
{
31+
if (scriptblockAst.ProcessBlock != null
32+
|| scriptblockAst.ParamBlock?.Attributes == null
33+
|| scriptblockAst.ParamBlock.Parameters == null)
34+
{
35+
continue;
36+
}
37+
38+
foreach (ParameterAst paramAst in scriptblockAst.ParamBlock.Parameters)
39+
{
40+
foreach (AttributeBaseAst paramAstAttribute in paramAst.Attributes)
41+
{
42+
if (!(paramAstAttribute is AttributeAst paramAttributeAst)) { continue; }
43+
44+
if (paramAttributeAst.NamedArguments == null) { continue; }
45+
46+
foreach (NamedAttributeArgumentAst namedArgument in paramAttributeAst.NamedArguments)
47+
{
48+
if (!namedArgument.ArgumentName.Equals("valuefrompipeline", StringComparison.OrdinalIgnoreCase)
49+
&& !namedArgument.ArgumentName.Equals("valuefrompipelinebypropertyname", StringComparison.OrdinalIgnoreCase))
50+
{
51+
continue;
52+
}
53+
54+
yield return new DiagnosticRecord(
55+
string.Format(CultureInfo.CurrentCulture, Strings.UseProcessBlockForPipelineCommandError, paramAst.Name.VariablePath.UserPath),
56+
paramAst.Name.Extent,
57+
GetName(),
58+
DiagnosticSeverity.Warning,
59+
fileName,
60+
paramAst.Name.VariablePath.UserPath
61+
);
62+
}
63+
}
64+
}
65+
}
66+
}
67+
68+
public string GetName()
69+
{
70+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseProcessBlockForPipelineCommandName);
71+
}
72+
73+
public string GetCommonName()
74+
{
75+
return string.Format(CultureInfo.CurrentCulture, Strings.UseProcessBlockForPipelineCommandCommonName);
76+
}
77+
78+
public string GetDescription()
79+
{
80+
return string.Format(CultureInfo.CurrentCulture, Strings.UseProcessBlockForPipelineCommandDescription);
81+
}
82+
83+
public SourceType GetSourceType()
84+
{
85+
return SourceType.Builtin;
86+
}
87+
88+
public RuleSeverity GetSeverity()
89+
{
90+
return RuleSeverity.Warning;
91+
}
92+
93+
public string GetSourceName()
94+
{
95+
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
96+
}
97+
}
98+
}

Tests/Engine/GetScriptAnalyzerRule.tests.ps1

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Describe "Test Name parameters" {
5959

6060
It "get Rules with no parameters supplied" {
6161
$defaultRules = Get-ScriptAnalyzerRule
62-
$expectedNumRules = 60
62+
$expectedNumRules = 61
6363
if ((Test-PSEditionCoreClr) -or (Test-PSVersionV3) -or (Test-PSVersionV4))
6464
{
6565
# for PSv3 PSAvoidGlobalAliases is not shipped because

0 commit comments

Comments
 (0)