From 01b7a9956217099f6216f9bfa104036374eef420 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Thu, 20 Dec 2018 16:39:30 +0000 Subject: [PATCH 1/9] first working prototype: Invoke-Formatter get-childitem --- Engine/Formatter.cs | 3 +- Engine/Settings/CodeFormatting.psd1 | 7 +- Engine/Settings/CodeFormattingAllman.psd1 | 7 +- Engine/Settings/CodeFormattingOTBS.psd1 | 7 +- Engine/Settings/CodeFormattingStroustrup.psd1 | 7 +- Rules/UseCorrectCasing.cs | 203 ++++++++++++++++++ 6 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 Rules/UseCorrectCasing.cs diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index c07d7c5ef..d86127f2f 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -40,7 +40,8 @@ public static string Format( "PSPlaceOpenBrace", "PSUseConsistentWhitespace", "PSUseConsistentIndentation", - "PSAlignAssignmentStatement" + "PSAlignAssignmentStatement", + "PSUseCorrectCasing" }; var text = new EditableText(scriptDefinition); diff --git a/Engine/Settings/CodeFormatting.psd1 b/Engine/Settings/CodeFormatting.psd1 index 02a07a1da..dbd4dd31e 100644 --- a/Engine/Settings/CodeFormatting.psd1 +++ b/Engine/Settings/CodeFormatting.psd1 @@ -4,7 +4,8 @@ 'PSPlaceCloseBrace', 'PSUseConsistentWhitespace', 'PSUseConsistentIndentation', - 'PSAlignAssignmentStatement' + 'PSAlignAssignmentStatement', + 'PSUseCorrectCasing' ) Rules = @{ @@ -40,5 +41,9 @@ Enable = $true CheckHashtable = $true } + + PSUseCorrectCasing = @{ + Enable = $true + } } } diff --git a/Engine/Settings/CodeFormattingAllman.psd1 b/Engine/Settings/CodeFormattingAllman.psd1 index 15b0ecfbd..f8359b756 100644 --- a/Engine/Settings/CodeFormattingAllman.psd1 +++ b/Engine/Settings/CodeFormattingAllman.psd1 @@ -4,7 +4,8 @@ 'PSPlaceCloseBrace', 'PSUseConsistentWhitespace', 'PSUseConsistentIndentation', - 'PSAlignAssignmentStatement' + 'PSAlignAssignmentStatement', + 'PSUseCorrectCasing' ) Rules = @{ @@ -40,5 +41,9 @@ Enable = $true CheckHashtable = $true } + + PSUseCorrectCasing = @{ + Enable = $true + } } } diff --git a/Engine/Settings/CodeFormattingOTBS.psd1 b/Engine/Settings/CodeFormattingOTBS.psd1 index 589487235..13858193f 100644 --- a/Engine/Settings/CodeFormattingOTBS.psd1 +++ b/Engine/Settings/CodeFormattingOTBS.psd1 @@ -4,7 +4,8 @@ 'PSPlaceCloseBrace', 'PSUseConsistentWhitespace', 'PSUseConsistentIndentation', - 'PSAlignAssignmentStatement' + 'PSAlignAssignmentStatement', + 'PSUseCorrectCasing' ) Rules = @{ @@ -40,5 +41,9 @@ Enable = $true CheckHashtable = $true } + + PSUseCorrectCasing = @{ + Enable = $true + } } } diff --git a/Engine/Settings/CodeFormattingStroustrup.psd1 b/Engine/Settings/CodeFormattingStroustrup.psd1 index f40c83988..6637969a0 100644 --- a/Engine/Settings/CodeFormattingStroustrup.psd1 +++ b/Engine/Settings/CodeFormattingStroustrup.psd1 @@ -5,7 +5,8 @@ 'PSPlaceCloseBrace', 'PSUseConsistentWhitespace', 'PSUseConsistentIndentation', - 'PSAlignAssignmentStatement' + 'PSAlignAssignmentStatement', + 'PSUseCorrectCasing' ) Rules = @{ @@ -41,5 +42,9 @@ Enable = $true CheckHashtable = $true } + + PSUseCorrectCasing = @{ + Enable = $true + } } } diff --git a/Rules/UseCorrectCasing.cs b/Rules/UseCorrectCasing.cs new file mode 100644 index 000000000..6edfe0121 --- /dev/null +++ b/Rules/UseCorrectCasing.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +#if !CORECLR +using System.ComponentModel.Composition; +#endif +using System.Globalization; +using System.Linq; +using System.Management.Automation; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules +{ + /// + /// UseCorrectCasing: Check if cmdlet is cased correctly. + /// +#if !CORECLR + [Export(typeof(IScriptRule))] +#endif + public class UseCorrectCasing : ConfigurableRule //IScriptRule + { + /// + /// AnalyzeScript: Analyze the script to check if cmdlet alias is used. + /// + public override IEnumerable AnalyzeScript(Ast ast, string fileName) + { + if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + + // Finds all CommandAsts. + IEnumerable foundAsts = ast.FindAll(testAst => testAst is CommandAst, true); + + // Iterates all CommandAsts and check the command name. + foreach (CommandAst commandAst in foundAsts) + { + // Check if the command ast should be ignored + if (IgnoreCommandast(commandAst)) + { + continue; + } + + string commandName = commandAst.GetCommandName(); + + // Handles the exception caused by commands like, {& $PLINK $args 2> $TempErrorFile}. + // You can also review the remark section in following document, + // MSDN: CommandAst.GetCommandName Method + if (commandName == null) + { + continue; + } + + using (var powershell = System.Management.Automation.PowerShell.Create()) + { + var psCommand = powershell.AddCommand("Get-Command") + .AddParameter("Name", commandName) + .AddParameter("ErrorAction", "SilentlyContinue"); + + //if (commandName != null) + //{ + // psCommand.AddParameter("CommandType", commandType); + //} + + var commandInfo = psCommand.Invoke() + .FirstOrDefault(); + var name = commandInfo.Name; + var modulename = commandInfo.ModuleName; + var fullyqual = $"{modulename}\\{name}"; + + if (!name.Equals(commandName, StringComparison.Ordinal)) + { + yield return new DiagnosticRecord( + string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesError, commandName, name), + GetCommandExtent(commandAst), + GetName(), + DiagnosticSeverity.Warning, + fileName, + commandName, + suggestedCorrections: GetCorrectionExtent(commandAst, name)); + } + } + + } + } + + /// + /// Checks commandast of the form "[commandElement0] = [CommandElement2]". This typically occurs in a DSC configuration. + /// + private bool IgnoreCommandast(CommandAst cmdAst) + { + if (cmdAst.CommandElements.Count == 3) + { + var element = cmdAst.CommandElements[1] as StringConstantExpressionAst; + if (element != null && element.Value.Equals("=")) + { + return true; + } + } + + return false; + } + + /// + /// For a command like "gci -path c:", returns the extent of "gci" in the command + /// + private IScriptExtent GetCommandExtent(CommandAst commandAst) + { + var cmdName = commandAst.GetCommandName(); + foreach (var cmdElement in commandAst.CommandElements) + { + var stringConstExpressinAst = cmdElement as StringConstantExpressionAst; + if (stringConstExpressinAst != null) + { + if (stringConstExpressinAst.Value.Equals(cmdName)) + { + return stringConstExpressinAst.Extent; + } + } + } + return commandAst.Extent; + } + + /// + /// Creates a list containing suggested correction + /// + /// Command AST of an alias + /// Full name of the alias + /// Retruns a list of suggested corrections + private List GetCorrectionExtent(CommandAst cmdAst, string cmdletName) + { + var corrections = new List(); + var alias = cmdAst.GetCommandName(); + var description = string.Format( + CultureInfo.CurrentCulture, + Strings.AvoidUsingCmdletAliasesCorrectionDescription, + alias, + cmdletName); + var cmdExtent = GetCommandExtent(cmdAst); + corrections.Add(new CorrectionExtent( + cmdExtent.StartLineNumber, + cmdExtent.EndLineNumber, + cmdExtent.StartColumnNumber, + cmdExtent.EndColumnNumber, + cmdletName, + cmdAst.Extent.File, + description)); + return corrections; + } + + /// + /// GetName: Retrieves the name of this rule. + /// + /// The name of this rule + public override string GetName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), "UseCorrectCasing");//Strings.AvoidUsingCmdletAliasesName); + } + + /// + /// GetCommonName: Retrieves the common name of this rule. + /// + /// The common name of this rule + public override string GetCommonName() + { + return string.Format(CultureInfo.CurrentCulture, "UseCorrectCasing");// Strings.AvoidUsingCmdletAliasesCommonName); + } + + /// + /// GetDescription: Retrieves the description of this rule. + /// + /// The description of this rule + public override string GetDescription() + { + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesDescription); + } + + /// + /// GetSourceType: Retrieves the type of the rule, Builtin, Managed or Module. + /// + public override SourceType GetSourceType() + { + return SourceType.Builtin; + } + + /// + /// GetSeverity: Retrieves the severity of the rule: error, warning of information. + /// + /// + public override RuleSeverity GetSeverity() + { + return RuleSeverity.Warning; + } + + /// + /// GetSourceName: Retrieves the name of the module/assembly the rule is from. + /// + public override string GetSourceName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); + } + } +} \ No newline at end of file From b613a92900bb48960f3769f84e1c022591cb055b Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Fri, 21 Dec 2018 11:57:49 +0000 Subject: [PATCH 2/9] tweak for fully qualified cmdlets and add tests --- Rules/UseCorrectCasing.cs | 91 +++++++++----------------- Tests/Rules/UseCorrectCasing.tests.ps1 | 21 ++++++ 2 files changed, 53 insertions(+), 59 deletions(-) create mode 100644 Tests/Rules/UseCorrectCasing.tests.ps1 diff --git a/Rules/UseCorrectCasing.cs b/Rules/UseCorrectCasing.cs index 6edfe0121..db16fabc9 100644 --- a/Rules/UseCorrectCasing.cs +++ b/Rules/UseCorrectCasing.cs @@ -29,18 +29,11 @@ public override IEnumerable AnalyzeScript(Ast ast, string file { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - // Finds all CommandAsts. - IEnumerable foundAsts = ast.FindAll(testAst => testAst is CommandAst, true); + IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); // Iterates all CommandAsts and check the command name. - foreach (CommandAst commandAst in foundAsts) + foreach (CommandAst commandAst in commandAsts) { - // Check if the command ast should be ignored - if (IgnoreCommandast(commandAst)) - { - continue; - } - string commandName = commandAst.GetCommandName(); // Handles the exception caused by commands like, {& $PLINK $args 2> $TempErrorFile}. @@ -53,7 +46,7 @@ public override IEnumerable AnalyzeScript(Ast ast, string file using (var powershell = System.Management.Automation.PowerShell.Create()) { - var psCommand = powershell.AddCommand("Get-Command") + var getCommand = powershell.AddCommand("Get-Command") .AddParameter("Name", commandName) .AddParameter("ErrorAction", "SilentlyContinue"); @@ -62,45 +55,33 @@ public override IEnumerable AnalyzeScript(Ast ast, string file // psCommand.AddParameter("CommandType", commandType); //} - var commandInfo = psCommand.Invoke() - .FirstOrDefault(); - var name = commandInfo.Name; - var modulename = commandInfo.ModuleName; - var fullyqual = $"{modulename}\\{name}"; - - if (!name.Equals(commandName, StringComparison.Ordinal)) + var commandInfo = getCommand.Invoke().FirstOrDefault(); + if (commandInfo != null) { - yield return new DiagnosticRecord( - string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesError, commandName, name), - GetCommandExtent(commandAst), - GetName(), - DiagnosticSeverity.Warning, - fileName, - commandName, - suggestedCorrections: GetCorrectionExtent(commandAst, name)); + var shortName = commandInfo.Name; + var fullyqualifiedName = $"{commandInfo.ModuleName}\\{shortName}"; + + var isFullyQualified = commandName.Equals(fullyqualifiedName, StringComparison.OrdinalIgnoreCase); + var correctlyCasedCommandName = isFullyQualified ? fullyqualifiedName : shortName; + + + if (!commandName.Equals(correctlyCasedCommandName, StringComparison.Ordinal)) + { + yield return new DiagnosticRecord( + string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesError, commandName, shortName), + GetCommandExtent(commandAst), + GetName(), + DiagnosticSeverity.Warning, + fileName, + commandName, + suggestedCorrections: GetCorrectionExtent(commandAst, correctlyCasedCommandName)); + } } } } } - /// - /// Checks commandast of the form "[commandElement0] = [CommandElement2]". This typically occurs in a DSC configuration. - /// - private bool IgnoreCommandast(CommandAst cmdAst) - { - if (cmdAst.CommandElements.Count == 3) - { - var element = cmdAst.CommandElements[1] as StringConstantExpressionAst; - if (element != null && element.Value.Equals("=")) - { - return true; - } - } - - return false; - } - /// /// For a command like "gci -path c:", returns the extent of "gci" in the command /// @@ -121,31 +102,23 @@ private IScriptExtent GetCommandExtent(CommandAst commandAst) return commandAst.Extent; } - /// - /// Creates a list containing suggested correction - /// - /// Command AST of an alias - /// Full name of the alias - /// Retruns a list of suggested corrections - private List GetCorrectionExtent(CommandAst cmdAst, string cmdletName) + private IEnumerable GetCorrectionExtent(CommandAst commandAst, string correctlyCaseName) { - var corrections = new List(); - var alias = cmdAst.GetCommandName(); var description = string.Format( CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesCorrectionDescription, - alias, - cmdletName); - var cmdExtent = GetCommandExtent(cmdAst); - corrections.Add(new CorrectionExtent( + correctlyCaseName, + correctlyCaseName); + var cmdExtent = GetCommandExtent(commandAst); + var correction = new CorrectionExtent( cmdExtent.StartLineNumber, cmdExtent.EndLineNumber, cmdExtent.StartColumnNumber, cmdExtent.EndColumnNumber, - cmdletName, - cmdAst.Extent.File, - description)); - return corrections; + correctlyCaseName, + commandAst.Extent.File, + description); + yield return correction; } /// diff --git a/Tests/Rules/UseCorrectCasing.tests.ps1 b/Tests/Rules/UseCorrectCasing.tests.ps1 new file mode 100644 index 000000000..396413b38 --- /dev/null +++ b/Tests/Rules/UseCorrectCasing.tests.ps1 @@ -0,0 +1,21 @@ +Describe "UseCorrectCasing" { + It "corrects case of simple cmdlet" { + Invoke-Formatter 'get-childitem' | Should -Be 'Get-ChildItem' + } + + It "corrects case of fully qualified cmdlet" { + Invoke-Formatter 'Microsoft.PowerShell.management\get-childitem' | Should -Be 'Microsoft.PowerShell.Management\Get-ChildItem' + } + + It "corrects case of of cmdlet inside interpolated string" { + Invoke-Formatter '"$(get-childitem)"' | Should -Be '"$(get-childitem)"' + } + + It "corrects case of script function" { + function Invoke-DummyFunction + { + + } + Invoke-Formatter 'invoke-dummyFunction' | Should -Be 'Invoke-DummyFunction' + } +} \ No newline at end of file From d2aa9fc683802a69c206b0fa92584006724efd2e Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Fri, 21 Dec 2018 14:30:45 +0000 Subject: [PATCH 3/9] add rule documentation and fix 3 tests as part of that --- RuleDocumentation/README.md | 1 + RuleDocumentation/UseCorrectCasing.md | 27 ++++++++++++++++++++ Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 4 +-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 RuleDocumentation/UseCorrectCasing.md diff --git a/RuleDocumentation/README.md b/RuleDocumentation/README.md index a8ac87cf1..73ed09bc4 100644 --- a/RuleDocumentation/README.md +++ b/RuleDocumentation/README.md @@ -45,6 +45,7 @@ |[UseApprovedVerbs](./UseApprovedVerbs.md) | Warning | | |[UseBOMForUnicodeEncodedFile](./UseBOMForUnicodeEncodedFile.md) | Warning | | |[UseCmdletCorrectly](./UseCmdletCorrectly.md) | Warning | | +|[UseCorrectCasing](./UseCorrectCasing.md) | Information | | |[UseDeclaredVarsMoreThanAssignments](./UseDeclaredVarsMoreThanAssignments.md) | Warning | | |[UseLiteralInitializerForHashtable](./UseLiteralInitializerForHashtable.md) | Warning | | |[UseOutputTypeCorrectly](./UseOutputTypeCorrectly.md) | Information | | diff --git a/RuleDocumentation/UseCorrectCasing.md b/RuleDocumentation/UseCorrectCasing.md new file mode 100644 index 000000000..3fd4216c2 --- /dev/null +++ b/RuleDocumentation/UseCorrectCasing.md @@ -0,0 +1,27 @@ +# UseCorrectCasing + +**Severity Level: Information** + +## Description + +This is a style/formatting rule. PowerShell is case insensitive where applicable. The casing of cmdlet names does not matter but this rule ensures that the casing matches since most cmdlets start with an upper case and using that improves readability to the human eye. + +## How + +Use exact casing of the cmdlet, e.g. `Invoke-Command`. + +## Example + +### Wrong + +``` PowerShell +invoke-command { 'foo' } +} +``` + +### Correct + +``` PowerShell +Invoke-Command { 'foo' } +} +``` diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index ad0a10898..1631633e9 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -59,7 +59,7 @@ Describe "Test Name parameters" { It "get Rules with no parameters supplied" { $defaultRules = Get-ScriptAnalyzerRule - $expectedNumRules = 55 + $expectedNumRules = 56 if ((Test-PSEditionCoreClr) -or (Test-PSVersionV3) -or (Test-PSVersionV4)) { # for PSv3 PSAvoidGlobalAliases is not shipped because @@ -69,7 +69,7 @@ Describe "Test Name parameters" { # shipped because it uses APIs that are not present # in dotnet core. - $expectedNumRules-- + $expectedNumRules-- } $defaultRules.Count | Should -Be $expectedNumRules } From db83ede5822e89641d34f8b305b6034e666bb26b Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Fri, 21 Dec 2018 14:44:55 +0000 Subject: [PATCH 4/9] add resource strings --- RuleDocumentation/UseCorrectCasing.md | 2 +- Rules/Strings.Designer.cs | 36 +++++++++++++++++++++++++++ Rules/Strings.resx | 12 +++++++++ Rules/UseCorrectCasing.cs | 12 ++++----- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/RuleDocumentation/UseCorrectCasing.md b/RuleDocumentation/UseCorrectCasing.md index 3fd4216c2..c0d7a68f7 100644 --- a/RuleDocumentation/UseCorrectCasing.md +++ b/RuleDocumentation/UseCorrectCasing.md @@ -4,7 +4,7 @@ ## Description -This is a style/formatting rule. PowerShell is case insensitive where applicable. The casing of cmdlet names does not matter but this rule ensures that the casing matches since most cmdlets start with an upper case and using that improves readability to the human eye. +This is a style/formatting rule. PowerShell is case insensitive where applicable. The casing of cmdlet names does not matter but this rule ensures that the casing matches for consistency and also because most cmdlets start with an upper case and using that improves readability to the human eye. ## How diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index ba1d61a4d..3c1b8e251 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -2149,6 +2149,42 @@ internal static string UseConsistentWhitespaceName { } } + /// + /// Looks up a localized string similar to Use exact casing of cmdlet/function name.. + /// + internal static string UseCorrectCasingCommonName { + get { + return ResourceManager.GetString("UseCorrectCasingCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to For better readability and consistency, use the exact casing of the cmdlet/function.. + /// + internal static string UseCorrectCasingDescription { + get { + return ResourceManager.GetString("UseCorrectCasingDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cmdlet/Function does not match its exact casing '{0}'.. + /// + internal static string UseCorrectCasingError { + get { + return ResourceManager.GetString("UseCorrectCasingError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseCorrectCasingName. + /// + internal static string UseCorrectCasingName { + get { + return ResourceManager.GetString("UseCorrectCasingName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Extra Variables. /// diff --git a/Rules/Strings.resx b/Rules/Strings.resx index d4ebf259c..87f342911 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -990,4 +990,16 @@ PossibleIncorrectUsageOfRedirectionOperator + + Use exact casing of cmdlet/function name. + + + For better readability and consistency, use the exact casing of the cmdlet/function. + + + Cmdlet/Function does not match its exact casing '{0}'. + + + UseCorrectCasingName + \ No newline at end of file diff --git a/Rules/UseCorrectCasing.cs b/Rules/UseCorrectCasing.cs index db16fabc9..b66332518 100644 --- a/Rules/UseCorrectCasing.cs +++ b/Rules/UseCorrectCasing.cs @@ -68,7 +68,7 @@ public override IEnumerable AnalyzeScript(Ast ast, string file if (!commandName.Equals(correctlyCasedCommandName, StringComparison.Ordinal)) { yield return new DiagnosticRecord( - string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesError, commandName, shortName), + string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingError, commandName, shortName), GetCommandExtent(commandAst), GetName(), DiagnosticSeverity.Warning, @@ -106,7 +106,7 @@ private IEnumerable GetCorrectionExtent(CommandAst commandAst, { var description = string.Format( CultureInfo.CurrentCulture, - Strings.AvoidUsingCmdletAliasesCorrectionDescription, + Strings.UseCorrectCasingDescription, correctlyCaseName, correctlyCaseName); var cmdExtent = GetCommandExtent(commandAst); @@ -127,7 +127,7 @@ private IEnumerable GetCorrectionExtent(CommandAst commandAst, /// The name of this rule public override string GetName() { - return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), "UseCorrectCasing");//Strings.AvoidUsingCmdletAliasesName); + return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseCorrectCasingName); } /// @@ -136,7 +136,7 @@ public override string GetName() /// The common name of this rule public override string GetCommonName() { - return string.Format(CultureInfo.CurrentCulture, "UseCorrectCasing");// Strings.AvoidUsingCmdletAliasesCommonName); + return string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingCommonName); } /// @@ -145,7 +145,7 @@ public override string GetCommonName() /// The description of this rule public override string GetDescription() { - return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesDescription); + return string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingDescription); } /// @@ -162,7 +162,7 @@ public override SourceType GetSourceType() /// public override RuleSeverity GetSeverity() { - return RuleSeverity.Warning; + return RuleSeverity.Information; } /// From 0f7e48f9100777f503b03fffe22b5b28cdc2bc4d Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Fri, 21 Dec 2018 14:50:00 +0000 Subject: [PATCH 5/9] fix 1 test (severity) and docs test due to rule name --- Rules/Strings.Designer.cs | 2 +- Rules/Strings.resx | 2 +- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 3c1b8e251..46e444e7f 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -2177,7 +2177,7 @@ internal static string UseCorrectCasingError { } /// - /// Looks up a localized string similar to UseCorrectCasingName. + /// Looks up a localized string similar to UseCorrectCasing. /// internal static string UseCorrectCasingName { get { diff --git a/Rules/Strings.resx b/Rules/Strings.resx index 87f342911..5dd1741f7 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -1000,6 +1000,6 @@ Cmdlet/Function does not match its exact casing '{0}'. - UseCorrectCasingName + UseCorrectCasing \ No newline at end of file diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 1631633e9..b06f61117 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -157,7 +157,7 @@ Describe "TestSeverity" { It "filters rules based on multiple severity inputs"{ $rules = Get-ScriptAnalyzerRule -Severity Error,Information - $rules.Count | Should -Be 15 + $rules.Count | Should -Be 16 } It "takes lower case inputs" { From 2012a265f1365961cfad4d4e663e2152dffad4b2 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Fri, 21 Dec 2018 16:11:46 +0000 Subject: [PATCH 6/9] fix failing test using a workaround --- Tests/Rules/UseCorrectCasing.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Rules/UseCorrectCasing.tests.ps1 b/Tests/Rules/UseCorrectCasing.tests.ps1 index 396413b38..ac41df434 100644 --- a/Tests/Rules/UseCorrectCasing.tests.ps1 +++ b/Tests/Rules/UseCorrectCasing.tests.ps1 @@ -17,5 +17,6 @@ Describe "UseCorrectCasing" { } Invoke-Formatter 'invoke-dummyFunction' | Should -Be 'Invoke-DummyFunction' + Invoke-ScriptAnalyzer 'foo' # Workaround for this bug@ https://github.com/PowerShell/PSScriptAnalyzer/issues/1116 } } \ No newline at end of file From 42c54b026e6a6bccde7956233ab84141b79e3b88 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Fri, 21 Dec 2018 16:22:03 +0000 Subject: [PATCH 7/9] fix tests --- Tests/Rules/UseCorrectCasing.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Rules/UseCorrectCasing.tests.ps1 b/Tests/Rules/UseCorrectCasing.tests.ps1 index ac41df434..b7abc6cfd 100644 --- a/Tests/Rules/UseCorrectCasing.tests.ps1 +++ b/Tests/Rules/UseCorrectCasing.tests.ps1 @@ -17,6 +17,6 @@ Describe "UseCorrectCasing" { } Invoke-Formatter 'invoke-dummyFunction' | Should -Be 'Invoke-DummyFunction' - Invoke-ScriptAnalyzer 'foo' # Workaround for this bug@ https://github.com/PowerShell/PSScriptAnalyzer/issues/1116 + Invoke-ScriptAnalyzer -ScriptDefinition 'foo' # Workaround for this bug https://github.com/PowerShell/PSScriptAnalyzer/issues/1116 } } \ No newline at end of file From e9440ca931434f158530eb24381cc6df9e01da87 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 2 Feb 2019 15:34:32 +0000 Subject: [PATCH 8/9] remove workaround in test that has been fixed upstream --- Tests/Rules/UseCorrectCasing.tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/Rules/UseCorrectCasing.tests.ps1 b/Tests/Rules/UseCorrectCasing.tests.ps1 index b7abc6cfd..396413b38 100644 --- a/Tests/Rules/UseCorrectCasing.tests.ps1 +++ b/Tests/Rules/UseCorrectCasing.tests.ps1 @@ -17,6 +17,5 @@ Describe "UseCorrectCasing" { } Invoke-Formatter 'invoke-dummyFunction' | Should -Be 'Invoke-DummyFunction' - Invoke-ScriptAnalyzer -ScriptDefinition 'foo' # Workaround for this bug https://github.com/PowerShell/PSScriptAnalyzer/issues/1116 } } \ No newline at end of file From 5dae0a28990fb6cd7d3018d4a2053a50f374e5c7 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 2 Mar 2019 15:49:49 +0000 Subject: [PATCH 9/9] cleanup and use helper method for getting cached cmdlet data --- Rules/UseCorrectCasing.cs | 53 ++++++++++++++------------------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/Rules/UseCorrectCasing.cs b/Rules/UseCorrectCasing.cs index b66332518..490008b1d 100644 --- a/Rules/UseCorrectCasing.cs +++ b/Rules/UseCorrectCasing.cs @@ -9,8 +9,6 @@ using System.ComponentModel.Composition; #endif using System.Globalization; -using System.Linq; -using System.Management.Automation; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules { @@ -20,7 +18,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules #if !CORECLR [Export(typeof(IScriptRule))] #endif - public class UseCorrectCasing : ConfigurableRule //IScriptRule + public class UseCorrectCasing : ConfigurableRule { /// /// AnalyzeScript: Analyze the script to check if cmdlet alias is used. @@ -44,41 +42,28 @@ public override IEnumerable AnalyzeScript(Ast ast, string file continue; } - using (var powershell = System.Management.Automation.PowerShell.Create()) + var commandInfo = Helper.Instance.GetCommandInfo(commandName); + if (commandInfo == null) { - var getCommand = powershell.AddCommand("Get-Command") - .AddParameter("Name", commandName) - .AddParameter("ErrorAction", "SilentlyContinue"); + continue; + } - //if (commandName != null) - //{ - // psCommand.AddParameter("CommandType", commandType); - //} + var shortName = commandInfo.Name; + var fullyqualifiedName = $"{commandInfo.ModuleName}\\{shortName}"; + var isFullyQualified = commandName.Equals(fullyqualifiedName, StringComparison.OrdinalIgnoreCase); + var correctlyCasedCommandName = isFullyQualified ? fullyqualifiedName : shortName; - var commandInfo = getCommand.Invoke().FirstOrDefault(); - if (commandInfo != null) - { - var shortName = commandInfo.Name; - var fullyqualifiedName = $"{commandInfo.ModuleName}\\{shortName}"; - - var isFullyQualified = commandName.Equals(fullyqualifiedName, StringComparison.OrdinalIgnoreCase); - var correctlyCasedCommandName = isFullyQualified ? fullyqualifiedName : shortName; - - - if (!commandName.Equals(correctlyCasedCommandName, StringComparison.Ordinal)) - { - yield return new DiagnosticRecord( - string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingError, commandName, shortName), - GetCommandExtent(commandAst), - GetName(), - DiagnosticSeverity.Warning, - fileName, - commandName, - suggestedCorrections: GetCorrectionExtent(commandAst, correctlyCasedCommandName)); - } - } + if (!commandName.Equals(correctlyCasedCommandName, StringComparison.Ordinal)) + { + yield return new DiagnosticRecord( + string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingError, commandName, shortName), + GetCommandExtent(commandAst), + GetName(), + DiagnosticSeverity.Warning, + fileName, + commandName, + suggestedCorrections: GetCorrectionExtent(commandAst, correctlyCasedCommandName)); } - } }