Skip to content

Commit 8db488d

Browse files
authored
Add AvoidMultipleTypeAttributes rule (#1705)
1 parent 6dbf6a1 commit 8db488d

File tree

7 files changed

+247
-2
lines changed

7 files changed

+247
-2
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# AvoidMultipleTypeAttributes
2+
3+
**Severity Level: Warning**
4+
5+
## Description
6+
7+
Parameters should not have more than one type specifier. Multiple type specifiers on parameters will cause a runtime error.
8+
9+
## How
10+
11+
Ensure each parameter has only 1 type specifier.
12+
13+
## Example
14+
15+
### Wrong
16+
17+
``` PowerShell
18+
function Test-Script
19+
{
20+
[CmdletBinding()]
21+
Param
22+
(
23+
[String]
24+
$Param1,
25+
26+
[switch]
27+
[bool]
28+
$Switch
29+
)
30+
...
31+
}
32+
```
33+
34+
### Correct
35+
36+
``` PowerShell
37+
function Test-Script
38+
{
39+
[CmdletBinding()]
40+
Param
41+
(
42+
[String]
43+
$Param1,
44+
45+
[switch]
46+
$Switch
47+
)
48+
...
49+
}
50+
```

RuleDocumentation/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
|[AvoidGlobalVars](./AvoidGlobalVars.md) | Warning | |
1414
|[AvoidInvokingEmptyMembers](./AvoidInvokingEmptyMembers.md) | Warning | |
1515
|[AvoidLongLines](./AvoidLongLines.md) | Warning | |
16+
|[AvoidMultipleTypeAttributes](./AvoidMultipleTypeAttributes.md) | Warning | |
1617
|[AvoidOverwritingBuiltInCmdlets](./AvoidOverwritingBuiltInCmdlets.md) | Warning | |
1718
|[AvoidNullOrEmptyHelpMessageAttribute](./AvoidNullOrEmptyHelpMessageAttribute.md) | Warning | |
1819
|[AvoidShouldContinueWithoutForce](./AvoidShouldContinueWithoutForce.md) | Warning | |

Rules/AvoidMultipleTypeAttributes.cs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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.Linq;
7+
using System.Management.Automation.Language;
8+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
9+
#if !CORECLR
10+
using System.ComponentModel.Composition;
11+
#endif
12+
using System.Globalization;
13+
14+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
15+
{
16+
/// <summary>
17+
/// AvoidMultipleTypeAttributes: Check that parameter does not be assigned to multiple types.
18+
/// </summary>
19+
#if !CORECLR
20+
[Export(typeof(IScriptRule))]
21+
#endif
22+
public sealed class AvoidMultipleTypeAttributes : IScriptRule
23+
{
24+
/// <summary>
25+
/// AvoidMultipleTypeAttributes: Check that parameter does not be assigned to multiple types.
26+
/// </summary>
27+
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
28+
{
29+
if (ast is null)
30+
{
31+
throw new ArgumentNullException(Strings.NullAstErrorMessage);
32+
}
33+
34+
// Finds all ParamAsts.
35+
IEnumerable<Ast> paramAsts = ast.FindAll(testAst => testAst is ParameterAst, searchNestedScriptBlocks: true);
36+
37+
// Iterates all ParamAsts and check the number of its types.
38+
foreach (ParameterAst paramAst in paramAsts)
39+
{
40+
if (paramAst.Attributes.Where(typeAst => typeAst is TypeConstraintAst).Count() > 1)
41+
{
42+
yield return new DiagnosticRecord(
43+
String.Format(CultureInfo.CurrentCulture, Strings.AvoidMultipleTypeAttributesError, paramAst.Name),
44+
paramAst.Name.Extent,
45+
GetName(),
46+
DiagnosticSeverity.Warning,
47+
fileName);
48+
}
49+
}
50+
}
51+
52+
/// <summary>
53+
/// GetName: Retrieves the name of this rule.
54+
/// </summary>
55+
/// <returns>The name of this rule</returns>
56+
public string GetName()
57+
{
58+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidMultipleTypeAttributesName);
59+
}
60+
61+
/// <summary>
62+
/// GetCommonName: Retrieves the common name of this rule.
63+
/// </summary>
64+
/// <returns>The common name of this rule</returns>
65+
public string GetCommonName()
66+
{
67+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidMultipleTypeAttributesCommonName);
68+
}
69+
70+
/// <summary>
71+
/// GetDescription: Retrieves the description of this rule.
72+
/// </summary>
73+
/// <returns>The description of this rule</returns>
74+
public string GetDescription()
75+
{
76+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidMultipleTypeAttributesDescription);
77+
}
78+
79+
/// <summary>
80+
/// GetSourceType: Retrieves the type of the rule, Builtin, Managed or Module.
81+
/// </summary>
82+
public SourceType GetSourceType()
83+
{
84+
return SourceType.Builtin;
85+
}
86+
87+
/// <summary>
88+
/// GetSeverity: Retrieves the severity of the rule: error, warning or information.
89+
/// </summary>
90+
/// <returns></returns>
91+
public RuleSeverity GetSeverity()
92+
{
93+
return RuleSeverity.Warning;
94+
}
95+
96+
/// <summary>
97+
/// GetSourceName: Retrieves the name of the module/assembly the rule is from.
98+
/// </summary>
99+
public string GetSourceName()
100+
{
101+
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
102+
}
103+
}
104+
}

Rules/Strings.Designer.cs

Lines changed: 37 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rules/Strings.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,4 +1152,16 @@
11521152
<data name="InvalidSyntaxAroundProcessBlockError" xml:space="preserve">
11531153
<value>When using an explicit process block, no preceding code is allowed, only begin, end and dynamicparams blocks.</value>
11541154
</data>
1155+
<data name="AvoidMultipleTypeAttributesCommonName" xml:space="preserve">
1156+
<value>Avoid multiple type specifiers on parameters</value>
1157+
</data>
1158+
<data name="AvoidMultipleTypeAttributesDescription" xml:space="preserve">
1159+
<value>Prameter should not have more than one type specifier.</value>
1160+
</data>
1161+
<data name="AvoidMultipleTypeAttributesError" xml:space="preserve">
1162+
<value>Parameter '{0}' has more than one type specifier.</value>
1163+
</data>
1164+
<data name="AvoidMultipleTypeAttributesName" xml:space="preserve">
1165+
<value>AvoidMultipleTypeAttributes</value>
1166+
</data>
11551167
</root>

Tests/Engine/GetScriptAnalyzerRule.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Describe "Test Name parameters" {
6363

6464
It "get Rules with no parameters supplied" {
6565
$defaultRules = Get-ScriptAnalyzerRule
66-
$expectedNumRules = 65
66+
$expectedNumRules = 66
6767
if ($PSVersionTable.PSVersion.Major -le 4)
6868
{
6969
# for PSv3 PSAvoidGlobalAliases is not shipped because
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
BeforeAll {
5+
$ruleName = "AvoidMultipleTypeAttributes"
6+
7+
$settings = @{
8+
IncludeRules = @($ruleName)
9+
}
10+
}
11+
12+
Describe 'AvoidMultipleTypeAttributes' {
13+
It 'Correctly diagnoses and corrects <Script>' -TestCases @(
14+
@{ Script = 'function F1 ($s1, $p1){}' }
15+
@{ Script = 'function F2 ([int] $s2, [int] $p2){}' }
16+
@{ Script = 'function F3 ([int][switch] $s3, [int] $p3){}';Extent = @{ StartCol = 28; EndCol = 31 }; Message = 'Parameter ''$s3'' has more than one type specifier.' }
17+
@{ Script = 'function F4 ([int][ref] $s4, [int] $p4){}';Extent = @{ StartCol = 25; EndCol = 28 }; Message = 'Parameter ''$s4'' has more than one type specifier.' }
18+
@{ Script = 'function F5 ([int][switch][boolean] $s5, [int] $p5){}';Extent = @{ StartCol = 37; EndCol = 40 }; Message = 'Parameter ''$s5'' has more than one type specifier.' }
19+
@{ Script = 'function F6 ([ValidateSet()][int] $s6, [int] $p6){}' }
20+
@{ Script = 'function F7 ([Parameter(Mandatory=$true)][ValidateSet()][int] $s7, [int] $p7){}' }
21+
) {
22+
param([string]$Script, $Extent, $Message)
23+
24+
$diagnostics = Invoke-ScriptAnalyzer -ScriptDefinition $Script
25+
26+
if (-not $Extent)
27+
{
28+
$diagnostics | Should -BeNullOrEmpty
29+
return
30+
}
31+
32+
$expectedStartLine = if ($Extent.StartLine) { $Extent.StartLine } else { 1 }
33+
$expectedEndLine = if ($Extent.EndLine) { $Extent.EndLine } else { 1 }
34+
35+
$diagnostics.Extent.StartLineNumber | Should -BeExactly $expectedStartLine
36+
$diagnostics.Extent.EndLineNumber | Should -BeExactly $expectedEndLine
37+
$diagnostics.Extent.StartColumnNumber | Should -BeExactly $Extent.StartCol
38+
$diagnostics.Extent.EndColumnNumber | Should -BeExactly $Extent.EndCol
39+
40+
$diagnostics.Message | Should -BeExactly $Message
41+
}
42+
}

0 commit comments

Comments
 (0)