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