diff --git a/RuleDocumentation/AvoidMultipleTypeAttributes.md b/RuleDocumentation/AvoidMultipleTypeAttributes.md
new file mode 100644
index 000000000..5143042a2
--- /dev/null
+++ b/RuleDocumentation/AvoidMultipleTypeAttributes.md
@@ -0,0 +1,50 @@
+# AvoidMultipleTypeAttributes
+
+**Severity Level: Warning**
+
+## Description
+
+Parameters should not have more than one type specifier. Multiple type specifiers on parameters will cause a runtime error.
+
+## How
+
+Ensure each parameter has only 1 type specifier.
+
+## Example
+
+### Wrong
+
+``` PowerShell
+function Test-Script
+{
+ [CmdletBinding()]
+ Param
+ (
+ [String]
+ $Param1,
+
+ [switch]
+ [bool]
+ $Switch
+ )
+ ...
+}
+```
+
+### Correct
+
+``` PowerShell
+function Test-Script
+{
+ [CmdletBinding()]
+ Param
+ (
+ [String]
+ $Param1,
+
+ [switch]
+ $Switch
+ )
+ ...
+}
+```
diff --git a/RuleDocumentation/README.md b/RuleDocumentation/README.md
index 551ad55f5..25338085a 100644
--- a/RuleDocumentation/README.md
+++ b/RuleDocumentation/README.md
@@ -13,6 +13,7 @@
|[AvoidGlobalVars](./AvoidGlobalVars.md) | Warning | |
|[AvoidInvokingEmptyMembers](./AvoidInvokingEmptyMembers.md) | Warning | |
|[AvoidLongLines](./AvoidLongLines.md) | Warning | |
+|[AvoidMultipleTypeAttributes](./AvoidMultipleTypeAttributes.md) | Warning | |
|[AvoidOverwritingBuiltInCmdlets](./AvoidOverwritingBuiltInCmdlets.md) | Warning | |
|[AvoidNullOrEmptyHelpMessageAttribute](./AvoidNullOrEmptyHelpMessageAttribute.md) | Warning | |
|[AvoidShouldContinueWithoutForce](./AvoidShouldContinueWithoutForce.md) | Warning | |
diff --git a/Rules/AvoidMultipleTypeAttributes.cs b/Rules/AvoidMultipleTypeAttributes.cs
new file mode 100644
index 000000000..77f63de21
--- /dev/null
+++ b/Rules/AvoidMultipleTypeAttributes.cs
@@ -0,0 +1,104 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation.Language;
+using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
+#if !CORECLR
+using System.ComponentModel.Composition;
+#endif
+using System.Globalization;
+
+namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
+{
+ ///
+ /// AvoidMultipleTypeAttributes: Check that parameter does not be assigned to multiple types.
+ ///
+#if !CORECLR
+ [Export(typeof(IScriptRule))]
+#endif
+ public sealed class AvoidMultipleTypeAttributes : IScriptRule
+ {
+ ///
+ /// AvoidMultipleTypeAttributes: Check that parameter does not be assigned to multiple types.
+ ///
+ public IEnumerable AnalyzeScript(Ast ast, string fileName)
+ {
+ if (ast is null)
+ {
+ throw new ArgumentNullException(Strings.NullAstErrorMessage);
+ }
+
+ // Finds all ParamAsts.
+ IEnumerable paramAsts = ast.FindAll(testAst => testAst is ParameterAst, searchNestedScriptBlocks: true);
+
+ // Iterates all ParamAsts and check the number of its types.
+ foreach (ParameterAst paramAst in paramAsts)
+ {
+ if (paramAst.Attributes.Where(typeAst => typeAst is TypeConstraintAst).Count() > 1)
+ {
+ yield return new DiagnosticRecord(
+ String.Format(CultureInfo.CurrentCulture, Strings.AvoidMultipleTypeAttributesError, paramAst.Name),
+ paramAst.Name.Extent,
+ GetName(),
+ DiagnosticSeverity.Warning,
+ fileName);
+ }
+ }
+ }
+
+ ///
+ /// GetName: Retrieves the name of this rule.
+ ///
+ /// The name of this rule
+ public string GetName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidMultipleTypeAttributesName);
+ }
+
+ ///
+ /// GetCommonName: Retrieves the common name of this rule.
+ ///
+ /// The common name of this rule
+ public string GetCommonName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.AvoidMultipleTypeAttributesCommonName);
+ }
+
+ ///
+ /// GetDescription: Retrieves the description of this rule.
+ ///
+ /// The description of this rule
+ public string GetDescription()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.AvoidMultipleTypeAttributesDescription);
+ }
+
+ ///
+ /// GetSourceType: Retrieves the type of the rule, Builtin, Managed or Module.
+ ///
+ public SourceType GetSourceType()
+ {
+ return SourceType.Builtin;
+ }
+
+ ///
+ /// GetSeverity: Retrieves the severity of the rule: error, warning or information.
+ ///
+ ///
+ public RuleSeverity GetSeverity()
+ {
+ return RuleSeverity.Warning;
+ }
+
+ ///
+ /// GetSourceName: Retrieves the name of the module/assembly the rule is from.
+ ///
+ public string GetSourceName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
+ }
+ }
+}
diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs
index 70cda2d94..9fead171c 100644
--- a/Rules/Strings.Designer.cs
+++ b/Rules/Strings.Designer.cs
@@ -465,6 +465,42 @@ internal static string AvoidLongLinesName {
}
}
+ ///
+ /// Looks up a localized string similar to Avoid multiple type specifiers on parameters.
+ ///
+ internal static string AvoidMultipleTypeAttributesCommonName {
+ get {
+ return ResourceManager.GetString("AvoidMultipleTypeAttributesCommonName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Prameter should not have more than one type specifier..
+ ///
+ internal static string AvoidMultipleTypeAttributesDescription {
+ get {
+ return ResourceManager.GetString("AvoidMultipleTypeAttributesDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Parameter '{0}' has more than one type specifier..
+ ///
+ internal static string AvoidMultipleTypeAttributesError {
+ get {
+ return ResourceManager.GetString("AvoidMultipleTypeAttributesError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to AvoidMultipleTypeAttributes.
+ ///
+ internal static string AvoidMultipleTypeAttributesName {
+ get {
+ return ResourceManager.GetString("AvoidMultipleTypeAttributesName", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Avoid using null or empty HelpMessage parameter attribute..
///
@@ -1285,7 +1321,7 @@ internal static string DscTestsPresentNoTestsError {
}
///
- /// Looks up a localized string similar to When using a process block, only a begin or end block is allowed inside the function but no code..
+ /// Looks up a localized string similar to When using an explicit process block, no preceding code is allowed, only begin, end and dynamicparams blocks..
///
internal static string InvalidSyntaxAroundProcessBlockError {
get {
diff --git a/Rules/Strings.resx b/Rules/Strings.resx
index 120ead158..da1032671 100644
--- a/Rules/Strings.resx
+++ b/Rules/Strings.resx
@@ -1152,4 +1152,16 @@
When using an explicit process block, no preceding code is allowed, only begin, end and dynamicparams blocks.
+
+ Avoid multiple type specifiers on parameters
+
+
+ Prameter should not have more than one type specifier.
+
+
+ Parameter '{0}' has more than one type specifier.
+
+
+ AvoidMultipleTypeAttributes
+
diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1
index e5e2159bd..50a6f0077 100644
--- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1
+++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1
@@ -63,7 +63,7 @@ Describe "Test Name parameters" {
It "get Rules with no parameters supplied" {
$defaultRules = Get-ScriptAnalyzerRule
- $expectedNumRules = 65
+ $expectedNumRules = 66
if ($PSVersionTable.PSVersion.Major -le 4)
{
# for PSv3 PSAvoidGlobalAliases is not shipped because
diff --git a/Tests/Rules/AvoidMultipleTypeAttributes.tests.ps1 b/Tests/Rules/AvoidMultipleTypeAttributes.tests.ps1
new file mode 100644
index 000000000..5d448e64c
--- /dev/null
+++ b/Tests/Rules/AvoidMultipleTypeAttributes.tests.ps1
@@ -0,0 +1,42 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+BeforeAll {
+ $ruleName = "AvoidMultipleTypeAttributes"
+
+ $settings = @{
+ IncludeRules = @($ruleName)
+ }
+}
+
+Describe 'AvoidMultipleTypeAttributes' {
+ It 'Correctly diagnoses and corrects