From cdcea4ea7001a815ae6365ad61853d505a3e83e7 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:07:37 +0000 Subject: [PATCH 01/44] Make UseSingularNouns rule work for PowerShell Core versions --- RuleDocumentation/README.md | 2 +- Rules/Rules.csproj | 3 +- Rules/UseSingularNouns.cs | 35 +++++++++++++++---- .../UseSingularNounsReservedVerbs.tests.ps1 | 2 +- build.psm1 | 3 ++ tools/releaseBuild/signing.xml | 1 + 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/RuleDocumentation/README.md b/RuleDocumentation/README.md index fbe0f5cbd..551ad55f5 100644 --- a/RuleDocumentation/README.md +++ b/RuleDocumentation/README.md @@ -57,7 +57,7 @@ |[UseProcessBlockForPipelineCommand](./UseProcessBlockForPipelineCommand.md) | Warning | | |[UsePSCredentialType](./UsePSCredentialType.md) | Warning | | |[UseShouldProcessForStateChangingFunctions](./UseShouldProcessForStateChangingFunctions.md) | Warning | | -|[UseSingularNouns*](./UseSingularNouns.md) | Warning | | +|[UseSingularNouns](./UseSingularNouns.md) | Warning | | |[UseSupportsShouldProcess](./UseSupportsShouldProcess.md) | Warning | | |[UseToExportFieldsInManifest](./UseToExportFieldsInManifest.md) | Warning | | |[UseCompatibleCmdlets](./UseCompatibleCmdlets.md) | Warning | Yes | diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index 2fcf8f225..c00c6bd2b 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -7,6 +7,7 @@ 1.19.1 Rules Microsoft.Windows.PowerShell.ScriptAnalyzer + PackageReference @@ -18,7 +19,7 @@ - + diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 2a6fdd9d5..9dfe5d82f 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -15,7 +15,12 @@ using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +#if !CORECLR using System.ComponentModel.Composition; +#else +using PluralizationService; +using PluralizationService.English; +#endif using System.Globalization; using System.Text.RegularExpressions; @@ -25,8 +30,11 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules /// CmdletSingularNoun: Analyzes scripts to check that all defined cmdlets use singular nouns. /// /// - [Export(typeof(IScriptRule))] - public class CmdletSingularNoun : IScriptRule { +#if !CORECLR +[Export(typeof(IScriptRule))] +#endif + public class CmdletSingularNoun : IScriptRule + { private readonly string[] nounAllowList = { @@ -39,13 +47,22 @@ public class CmdletSingularNoun : IScriptRule { /// /// /// - public IEnumerable AnalyzeScript(Ast ast, string fileName) { + public IEnumerable AnalyzeScript(Ast ast, string fileName) + { if (ast == null) throw new ArgumentNullException(Strings.NullCommandInfoError); IEnumerable funcAsts = ast.FindAll(item => item is FunctionDefinitionAst, true); char[] funcSeperator = { '-' }; string[] funcNamePieces = new string[2]; + var usCultureInfo = new CultureInfo("en-US"); +#if !CORECLR + var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); +#else + var pluralizationApiBuilder = new PluralizationService.PluralizationApiBuilder(); + pluralizationApiBuilder.AddEnglishProvider(); + IPluralizationApi pluralizationService = pluralizationApiBuilder.Build(); +#endif foreach (FunctionDefinitionAst funcAst in funcAsts) { @@ -57,15 +74,18 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { // Convert the noun part of the function into a series of space delimited words // This helps the PluralizationService to provide an accurate determination about the plurality of the string nounPart = SplitCamelCaseString(nounPart); - var words = nounPart.Split(new char [] { ' ' }); + var words = nounPart.Split(new char[] { ' ' }); var noun = words.LastOrDefault(); if (noun == null) { continue; } - var ps = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(CultureInfo.GetCultureInfo("en-us")); - if (!ps.IsSingular(noun) && ps.IsPlural(noun)) +#if !CORECLR + if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) +#else + if (!pluralizationService.IsSingular(noun, usCultureInfo) && pluralizationService.IsPlural(noun, usCultureInfo)) +#endif { IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) @@ -106,7 +126,8 @@ public string GetCommonName() /// GetDescription: Retrieves the description of this rule. /// /// The description of this rule - public string GetDescription() { + public string GetDescription() + { return string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsDescription); } diff --git a/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 b/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 index b712bc6c0..b1b6e7724 100644 --- a/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 +++ b/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 @@ -15,7 +15,7 @@ BeforeAll { } # UseSingularNouns rule doesn't exist in the non desktop version of PSScriptAnalyzer due to missing .Net Pluralization API -Describe "UseSingularNouns" -Skip:$IsCoreCLR { +Describe "UseSingularNouns" { Context "When there are violations" { It "has a cmdlet singular noun violation" { $nounViolations.Count | Should -Be 1 diff --git a/build.psm1 b/build.psm1 index 82b736861..50acbdab2 100644 --- a/build.psm1 +++ b/build.psm1 @@ -320,6 +320,9 @@ function Start-ScriptAnalyzerBuild } Copy-Item -path $nsoft -Destination $destinationDirBinaries } + else { + Copy-Item -path "$projectRoot\Rules\bin\${buildConfiguration}\${framework}\PluralizeService.Core.dll" -Destination $destinationDirBinaries + } Pop-Location } diff --git a/tools/releaseBuild/signing.xml b/tools/releaseBuild/signing.xml index c6d35f47e..90ff91594 100644 --- a/tools/releaseBuild/signing.xml +++ b/tools/releaseBuild/signing.xml @@ -24,6 +24,7 @@ + From 8b75e063893db193ce458dd878fde32f6b7fe564 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:14:46 +0000 Subject: [PATCH 02/44] fix test --- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 664be838a..df94fdc89 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -64,14 +64,11 @@ Describe "Test Name parameters" { It "get Rules with no parameters supplied" { $defaultRules = Get-ScriptAnalyzerRule $expectedNumRules = 65 - if ($IsCoreCLR -or ($PSVersionTable.PSVersion.Major -eq 3) -or ($PSVersionTable.PSVersion.Major -eq 4)) + if (($PSVersionTable.PSVersion.Major -eq 3) -or ($PSVersionTable.PSVersion.Major -eq 4)) { # for PSv3 PSAvoidGlobalAliases is not shipped because # it uses StaticParameterBinder.BindCommand which is # available only on PSv4 and above - # for PowerShell Core, PSUseSingularNouns is not - # shipped because it uses APIs that are not present - # in dotnet core. $expectedNumRules-- } From fb51900dd82a5e56d403b335cd65db22b3f59c4a Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:18:05 +0000 Subject: [PATCH 03/44] fix build --- Rules/Rules.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index c00c6bd2b..255022fa5 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -7,7 +7,7 @@ 1.19.1 Rules Microsoft.Windows.PowerShell.ScriptAnalyzer - PackageReference + true From e0132d24e865f790d8cf338650b33bd9ec265f25 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:24:46 +0000 Subject: [PATCH 04/44] try remove NuGet.Config that led to build failure and use same old getculture api --- Rules/UseSingularNouns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 9dfe5d82f..b34130dfd 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -55,7 +55,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) char[] funcSeperator = { '-' }; string[] funcNamePieces = new string[2]; - var usCultureInfo = new CultureInfo("en-US"); + var usCultureInfo = CultureInfo.GetCultureInfo("en-us"); #if !CORECLR var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); #else From 842b86612eaf4bc5bc7d4b01e832579264aaa939 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:32:41 +0000 Subject: [PATCH 05/44] fix more tests --- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index 3a94699bd..a1b8b7380 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -270,12 +270,7 @@ Describe "Test IncludeRule" { } It "includes the given rules" { - # CoreCLR version of PSScriptAnalyzer does not contain PSUseSingularNouns rule $expectedNumViolations = 2 - if ($IsCoreCLR) - { - $expectedNumViolations = 1 - } $violations = Invoke-ScriptAnalyzer $PSScriptRoot\..\Rules\BadCmdlet.ps1 -IncludeRule $rules $violations.Count | Should -Be $expectedNumViolations } @@ -295,12 +290,7 @@ Describe "Test IncludeRule" { } It "includes 2 wildcardrules" { - # CoreCLR version of PSScriptAnalyzer does not contain PSUseSingularNouns rule $expectedNumViolations = 4 - if ($IsCoreCLR) - { - $expectedNumViolations = 3 - } $includeWildcard = Invoke-ScriptAnalyzer $PSScriptRoot\..\Rules\BadCmdlet.ps1 -IncludeRule $avoidRules $includeWildcard += Invoke-ScriptAnalyzer $PSScriptRoot\..\Rules\BadCmdlet.ps1 -IncludeRule $useRules $includeWildcard.Count | Should -Be $expectedNumViolations From 240f056fd8b4758dd705ce1a26015d33cc6970e6 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 10:28:57 +0000 Subject: [PATCH 06/44] fix build --- build.psm1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build.psm1 b/build.psm1 index 50acbdab2..f2d686f89 100644 --- a/build.psm1 +++ b/build.psm1 @@ -311,17 +311,17 @@ function Start-ScriptAnalyzerBuild $settingsFiles = Get-Childitem "$projectRoot\Engine\Settings" | ForEach-Object -MemberName FullName Publish-File $settingsFiles (Join-Path -Path $script:destinationDir -ChildPath Settings) + $rulesProjectOutputDir = if ($env:TF_BUILD) { + "$projectRoot\bin\${buildConfiguration}\${framework}" } + else { + "$projectRoot\Rules\bin\${buildConfiguration}\${framework}" + } if ($framework -eq 'net452') { - if ( $env:TF_BUILD ) { - $nsoft = "$projectRoot\bin\${buildConfiguration}\${framework}\Newtonsoft.Json.dll" - } - else { - $nsoft = "$projectRoot\Rules\bin\${buildConfiguration}\${framework}\Newtonsoft.Json.dll" - } + $nsoft = Join-Path $rulesProjectOutputDir 'Newtonsoft.Json.dll' Copy-Item -path $nsoft -Destination $destinationDirBinaries } else { - Copy-Item -path "$projectRoot\Rules\bin\${buildConfiguration}\${framework}\PluralizeService.Core.dll" -Destination $destinationDirBinaries + Copy-Item -Path (Join-Path $rulesProjectOutputDir 'PluralizeService.Core.dll') -Destination $destinationDirBinaries } Pop-Location From 532053c0495508777b972df259e2a70f57537d9f Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 10:31:12 +0000 Subject: [PATCH 07/44] fix last 2 test failures --- Tests/Documentation/RuleDocumentation.tests.ps1 | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Tests/Documentation/RuleDocumentation.tests.ps1 b/Tests/Documentation/RuleDocumentation.tests.ps1 index 6b0d93a51..f08d9689e 100644 --- a/Tests/Documentation/RuleDocumentation.tests.ps1 +++ b/Tests/Documentation/RuleDocumentation.tests.ps1 @@ -15,14 +15,8 @@ Describe "Validate rule documentation files" { }} | Sort-Object - # Remove rules from the diff list that aren't supported on PSCore - if (($PSVersionTable.PSVersion.Major -ge 6) -and ($PSVersionTable.PSEdition -eq "Core")) - { - $RulesNotSupportedInNetstandard2 = @("PSUseSingularNouns") - $docs = $docs | Where-Object {$RulesNotSupportedInNetstandard2 -notcontains $_} - $readmeRules = $readmeRules | Where-Object { $RulesNotSupportedInNetstandard2 -notcontains $_ } - } - elseif ($PSVersionTable.PSVersion.Major -eq 4) { + # Remove rules from the diff list that aren't supported on old PS version + if ($PSVersionTable.PSVersion.Major -eq 4) { $docs = $docs | Where-Object {$_ -notmatch '^PSAvoidGlobalAliases$'} $readmeRules = $readmeRules | Where-Object { $_ -notmatch '^PSAvoidGlobalAliases$' } } From 930997b4baa43fbe8a56871482b3bc00e493ca73 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sun, 7 Feb 2021 17:01:31 +0000 Subject: [PATCH 08/44] use Pluralize.NET library, which has MIT licence --- Rules/Rules.csproj | 4 ++-- Rules/UseSingularNouns.cs | 14 ++++++-------- build.psm1 | 2 +- tools/releaseBuild/signing.xml | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index 255022fa5..3baa05c44 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -7,7 +7,7 @@ 1.19.1 Rules Microsoft.Windows.PowerShell.ScriptAnalyzer - true + true @@ -19,7 +19,7 @@ - + diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index b34130dfd..b0494e400 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -18,8 +18,7 @@ #if !CORECLR using System.ComponentModel.Composition; #else -using PluralizationService; -using PluralizationService.English; +using Pluralize.NET; #endif using System.Globalization; using System.Text.RegularExpressions; @@ -55,13 +54,12 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) char[] funcSeperator = { '-' }; string[] funcNamePieces = new string[2]; - var usCultureInfo = CultureInfo.GetCultureInfo("en-us"); + #if !CORECLR - var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); + var usCultureInfo = CultureInfo.GetCultureInfo("en-us"); + var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); #else - var pluralizationApiBuilder = new PluralizationService.PluralizationApiBuilder(); - pluralizationApiBuilder.AddEnglishProvider(); - IPluralizationApi pluralizationService = pluralizationApiBuilder.Build(); + var pluralizationService = new Pluralizer(); #endif foreach (FunctionDefinitionAst funcAst in funcAsts) @@ -84,7 +82,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) #if !CORECLR if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) #else - if (!pluralizationService.IsSingular(noun, usCultureInfo) && pluralizationService.IsPlural(noun, usCultureInfo)) + if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) #endif { IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); diff --git a/build.psm1 b/build.psm1 index f2d686f89..58812f1cd 100644 --- a/build.psm1 +++ b/build.psm1 @@ -321,7 +321,7 @@ function Start-ScriptAnalyzerBuild Copy-Item -path $nsoft -Destination $destinationDirBinaries } else { - Copy-Item -Path (Join-Path $rulesProjectOutputDir 'PluralizeService.Core.dll') -Destination $destinationDirBinaries + Copy-Item -Path (Join-Path $rulesProjectOutputDir 'Pluralize.NET.dll') -Destination $destinationDirBinaries } Pop-Location diff --git a/tools/releaseBuild/signing.xml b/tools/releaseBuild/signing.xml index 90ff91594..25c44ff89 100644 --- a/tools/releaseBuild/signing.xml +++ b/tools/releaseBuild/signing.xml @@ -24,7 +24,7 @@ - + From 52ac58a1e3e7b6cf6f638380b4c867bcff506c49 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sun, 7 Feb 2021 17:02:06 +0000 Subject: [PATCH 09/44] cleanup diff --- NuGet.Config | 1 + 1 file changed, 1 insertion(+) diff --git a/NuGet.Config b/NuGet.Config index 80f5bd7fc..ddc51cc6d 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,5 +3,6 @@ + From f4c19d81a9692f396d6bcc14defbf6fc62e15558 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sun, 7 Feb 2021 17:02:34 +0000 Subject: [PATCH 10/44] whitespace --- NuGet.Config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index ddc51cc6d..acba4478b 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,6 +3,6 @@ - + From 9c99b9d7596bc5d1d43e856366a56b50193fb17a Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:01:56 -0700 Subject: [PATCH 11/44] Update SingularNouns rule approach --- Rules/UseSingularNouns.cs | 128 +++++++++++++------ Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 2 +- 2 files changed, 89 insertions(+), 41 deletions(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index b0494e400..0f32d05df 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -15,10 +15,10 @@ using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; -#if !CORECLR -using System.ComponentModel.Composition; -#else +#if CORECLR using Pluralize.NET; +#else +using System.ComponentModel.Composition; #endif using System.Globalization; using System.Text.RegularExpressions; @@ -55,48 +55,42 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) char[] funcSeperator = { '-' }; string[] funcNamePieces = new string[2]; -#if !CORECLR - var usCultureInfo = CultureInfo.GetCultureInfo("en-us"); - var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); -#else - var pluralizationService = new Pluralizer(); -#endif + var pluralizer = new PluralizerProxy(); foreach (FunctionDefinitionAst funcAst in funcAsts) { - if (funcAst.Name != null && funcAst.Name.Contains('-')) + if (funcAst.Name == null || !funcAst.Name.Contains('-')) + { + continue; + } + + string noun = GetLastWordInCmdlet(funcAst.Name); + + if (noun is null) { - funcNamePieces = funcAst.Name.Split(funcSeperator); - String nounPart = funcNamePieces[1]; - - // Convert the noun part of the function into a series of space delimited words - // This helps the PluralizationService to provide an accurate determination about the plurality of the string - nounPart = SplitCamelCaseString(nounPart); - var words = nounPart.Split(new char[] { ' ' }); - var noun = words.LastOrDefault(); - if (noun == null) + continue; + } + + if (pluralizer.CanOnlyBePlural(noun)) + { + IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); + + if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) { continue; } -#if !CORECLR - if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) -#else - if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) -#endif + if (extent is null) { - IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); - if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - if (null == extent) - { - extent = funcAst.Extent; - } - yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsError, funcAst.Name), - extent, GetName(), DiagnosticSeverity.Warning, fileName); + extent = funcAst.Extent; } + + yield return new DiagnosticRecord( + string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsError, funcAst.Name), + extent, + GetName(), + DiagnosticSeverity.Warning, + fileName); } } @@ -155,17 +149,71 @@ public string GetSourceName() } /// - /// SplitCamelCaseString: Splits a Camel Case'd string into individual words with space delimited + /// Gets the last word in a standard syntax, CamelCase cmdlet. + /// If the cmdlet name is non-standard, returns null. /// - private string SplitCamelCaseString(string input) + private string GetLastWordInCmdlet(string cmdletName) + { + if (string.IsNullOrEmpty(cmdletName)) + { + return null; + } + + // Cmdlet doesn't use CamelCase, so assume it's something like an initialism that shouldn't be singularized + if (!char.IsLower(cmdletName[^1])) + { + return null; + } + + for (int i = cmdletName.Length - 1; i >= 0; i--) + { + if (cmdletName[i] == '-') + { + // Cmdlet name ends in '-' -- we give up + if (i == cmdletName.Length - 1) + { + return null; + } + + // Return everything after the dash + return cmdletName.Substring(i + 1); + } + + // We just changed from lower case to upper, so we have the end word + if (char.IsUpper(cmdletName[i])) + { + return cmdletName.Substring(i); + } + } + + // We shouldn't ever get here since we should always eventually hit a '-' + // But if we do, assume this isn't supported cmdlet name + return null; + } + +#if CoreCLR + private class PluralizerProxy { - if (String.IsNullOrEmpty(input)) + private readonly Pluralizer _pluralizer; + + public PluralizerProxy() { - return String.Empty; + _pluralizer = new Pluralizer(); } - return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim(); + public bool CanOnlyBePlural(string noun) => + !_pluralizer.IsSingular(noun) && _pluralizer.IsPlural(noun); } +#else + private class PluralizerProxy + { + private static readonly PluralizationService s_pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService( + CultureInfo.GetCultureInfo('en-us')); + + public bool CanOnlyBePlural(string noun) => + !s_pluralizationService.IsSingular(noun) && s_pluralizationService.IsPlural(noun); + } +#endif } } diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index df94fdc89..e5e2159bd 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -64,7 +64,7 @@ Describe "Test Name parameters" { It "get Rules with no parameters supplied" { $defaultRules = Get-ScriptAnalyzerRule $expectedNumRules = 65 - if (($PSVersionTable.PSVersion.Major -eq 3) -or ($PSVersionTable.PSVersion.Major -eq 4)) + if ($PSVersionTable.PSVersion.Major -le 4) { # for PSv3 PSAvoidGlobalAliases is not shipped because # it uses StaticParameterBinder.BindCommand which is From 7389618291d245ed1228bc2836072eadb2583db6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:07:04 -0700 Subject: [PATCH 12/44] Fix dumb string characters --- Rules/UseSingularNouns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 0f32d05df..f9a0a3334 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -208,7 +208,7 @@ public bool CanOnlyBePlural(string noun) => private class PluralizerProxy { private static readonly PluralizationService s_pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService( - CultureInfo.GetCultureInfo('en-us')); + CultureInfo.GetCultureInfo("en-us")); public bool CanOnlyBePlural(string noun) => !s_pluralizationService.IsSingular(noun) && s_pluralizationService.IsPlural(noun); From 9327e6fb32f7287a725ae20191ec85a71643d52d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:18:34 -0700 Subject: [PATCH 13/44] Add NuGet.config back --- NuGet.Config | 1 - 1 file changed, 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index acba4478b..80f5bd7fc 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,6 +3,5 @@ - From 05ccc2b63575ad39e3afb992977be75313b11925 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:23:25 -0700 Subject: [PATCH 14/44] Fix namespacing --- Rules/UseSingularNouns.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index f9a0a3334..c83428b59 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -19,6 +19,7 @@ using Pluralize.NET; #else using System.ComponentModel.Composition; +using System.Data.Entity.Design.PluralizationServices; #endif using System.Globalization; using System.Text.RegularExpressions; @@ -207,7 +208,7 @@ public bool CanOnlyBePlural(string noun) => #else private class PluralizerProxy { - private static readonly PluralizationService s_pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService( + private static readonly PluralizationService s_pluralizationService = PluralizationService.CreateService( CultureInfo.GetCultureInfo("en-us")); public bool CanOnlyBePlural(string noun) => From ef21d48ea98cb9a9fde5b68a6c445ab71cae265a Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:26:15 -0700 Subject: [PATCH 15/44] Fix pragma casing --- Rules/UseSingularNouns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index c83428b59..0d827099c 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -192,7 +192,7 @@ private string GetLastWordInCmdlet(string cmdletName) return null; } -#if CoreCLR +#if CORECLR private class PluralizerProxy { private readonly Pluralizer _pluralizer; From d013b61add36c47e734f90f2cc89a4e4b6c8df73 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:29:36 -0700 Subject: [PATCH 16/44] Use old index syntax --- Rules/UseSingularNouns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 0d827099c..17d83d744 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -161,7 +161,7 @@ private string GetLastWordInCmdlet(string cmdletName) } // Cmdlet doesn't use CamelCase, so assume it's something like an initialism that shouldn't be singularized - if (!char.IsLower(cmdletName[^1])) + if (!char.IsLower(cmdletName[cmdletName.Length - 1])) { return null; } From a6f08d29b27cdc2dc9477fb7bf72dca037eee626 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:33:15 -0700 Subject: [PATCH 17/44] Change singularization for dash cases --- Rules/UseSingularNouns.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 17d83d744..d5462e658 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -170,14 +170,8 @@ private string GetLastWordInCmdlet(string cmdletName) { if (cmdletName[i] == '-') { - // Cmdlet name ends in '-' -- we give up - if (i == cmdletName.Length - 1) - { - return null; - } - - // Return everything after the dash - return cmdletName.Substring(i + 1); + // We got to the dash without seeing a CamelCase word, so nothing to singularize + return null; } // We just changed from lower case to upper, so we have the end word From 1402a5e5fb3028218c44535962060e17e3690520 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:35:40 -0700 Subject: [PATCH 18/44] Remove dead variables --- Rules/UseSingularNouns.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index d5462e658..179977e73 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -53,9 +53,6 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) IEnumerable funcAsts = ast.FindAll(item => item is FunctionDefinitionAst, true); - char[] funcSeperator = { '-' }; - string[] funcNamePieces = new string[2]; - var pluralizer = new PluralizerProxy(); foreach (FunctionDefinitionAst funcAst in funcAsts) From c40f6c679ec53355569dc050057d7f1dbcf1387e Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:07:37 +0000 Subject: [PATCH 19/44] Make UseSingularNouns rule work for PowerShell Core versions --- RuleDocumentation/README.md | 2 +- Rules/Rules.csproj | 3 +- Rules/UseSingularNouns.cs | 35 +++++++++++++++---- .../UseSingularNounsReservedVerbs.tests.ps1 | 2 +- build.psm1 | 3 ++ tools/releaseBuild/signing.xml | 1 + 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/RuleDocumentation/README.md b/RuleDocumentation/README.md index fbe0f5cbd..551ad55f5 100644 --- a/RuleDocumentation/README.md +++ b/RuleDocumentation/README.md @@ -57,7 +57,7 @@ |[UseProcessBlockForPipelineCommand](./UseProcessBlockForPipelineCommand.md) | Warning | | |[UsePSCredentialType](./UsePSCredentialType.md) | Warning | | |[UseShouldProcessForStateChangingFunctions](./UseShouldProcessForStateChangingFunctions.md) | Warning | | -|[UseSingularNouns*](./UseSingularNouns.md) | Warning | | +|[UseSingularNouns](./UseSingularNouns.md) | Warning | | |[UseSupportsShouldProcess](./UseSupportsShouldProcess.md) | Warning | | |[UseToExportFieldsInManifest](./UseToExportFieldsInManifest.md) | Warning | | |[UseCompatibleCmdlets](./UseCompatibleCmdlets.md) | Warning | Yes | diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index 157f4467d..61ad72071 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -7,6 +7,7 @@ 1.20.0 Rules Microsoft.Windows.PowerShell.ScriptAnalyzer + PackageReference @@ -18,7 +19,7 @@ - + diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 2a6fdd9d5..9dfe5d82f 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -15,7 +15,12 @@ using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +#if !CORECLR using System.ComponentModel.Composition; +#else +using PluralizationService; +using PluralizationService.English; +#endif using System.Globalization; using System.Text.RegularExpressions; @@ -25,8 +30,11 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules /// CmdletSingularNoun: Analyzes scripts to check that all defined cmdlets use singular nouns. /// /// - [Export(typeof(IScriptRule))] - public class CmdletSingularNoun : IScriptRule { +#if !CORECLR +[Export(typeof(IScriptRule))] +#endif + public class CmdletSingularNoun : IScriptRule + { private readonly string[] nounAllowList = { @@ -39,13 +47,22 @@ public class CmdletSingularNoun : IScriptRule { /// /// /// - public IEnumerable AnalyzeScript(Ast ast, string fileName) { + public IEnumerable AnalyzeScript(Ast ast, string fileName) + { if (ast == null) throw new ArgumentNullException(Strings.NullCommandInfoError); IEnumerable funcAsts = ast.FindAll(item => item is FunctionDefinitionAst, true); char[] funcSeperator = { '-' }; string[] funcNamePieces = new string[2]; + var usCultureInfo = new CultureInfo("en-US"); +#if !CORECLR + var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); +#else + var pluralizationApiBuilder = new PluralizationService.PluralizationApiBuilder(); + pluralizationApiBuilder.AddEnglishProvider(); + IPluralizationApi pluralizationService = pluralizationApiBuilder.Build(); +#endif foreach (FunctionDefinitionAst funcAst in funcAsts) { @@ -57,15 +74,18 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { // Convert the noun part of the function into a series of space delimited words // This helps the PluralizationService to provide an accurate determination about the plurality of the string nounPart = SplitCamelCaseString(nounPart); - var words = nounPart.Split(new char [] { ' ' }); + var words = nounPart.Split(new char[] { ' ' }); var noun = words.LastOrDefault(); if (noun == null) { continue; } - var ps = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(CultureInfo.GetCultureInfo("en-us")); - if (!ps.IsSingular(noun) && ps.IsPlural(noun)) +#if !CORECLR + if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) +#else + if (!pluralizationService.IsSingular(noun, usCultureInfo) && pluralizationService.IsPlural(noun, usCultureInfo)) +#endif { IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) @@ -106,7 +126,8 @@ public string GetCommonName() /// GetDescription: Retrieves the description of this rule. /// /// The description of this rule - public string GetDescription() { + public string GetDescription() + { return string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsDescription); } diff --git a/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 b/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 index b712bc6c0..b1b6e7724 100644 --- a/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 +++ b/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 @@ -15,7 +15,7 @@ BeforeAll { } # UseSingularNouns rule doesn't exist in the non desktop version of PSScriptAnalyzer due to missing .Net Pluralization API -Describe "UseSingularNouns" -Skip:$IsCoreCLR { +Describe "UseSingularNouns" { Context "When there are violations" { It "has a cmdlet singular noun violation" { $nounViolations.Count | Should -Be 1 diff --git a/build.psm1 b/build.psm1 index 434e1e432..0a3906db3 100644 --- a/build.psm1 +++ b/build.psm1 @@ -320,6 +320,9 @@ function Start-ScriptAnalyzerBuild } Copy-Item -path $nsoft -Destination $destinationDirBinaries } + else { + Copy-Item -path "$projectRoot\Rules\bin\${buildConfiguration}\${framework}\PluralizeService.Core.dll" -Destination $destinationDirBinaries + } Pop-Location } diff --git a/tools/releaseBuild/signing.xml b/tools/releaseBuild/signing.xml index c6d35f47e..90ff91594 100644 --- a/tools/releaseBuild/signing.xml +++ b/tools/releaseBuild/signing.xml @@ -24,6 +24,7 @@ + From d9f206526dff3a5d726dd000a6dc46e865fb310c Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:14:46 +0000 Subject: [PATCH 20/44] fix test --- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 664be838a..df94fdc89 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -64,14 +64,11 @@ Describe "Test Name parameters" { It "get Rules with no parameters supplied" { $defaultRules = Get-ScriptAnalyzerRule $expectedNumRules = 65 - if ($IsCoreCLR -or ($PSVersionTable.PSVersion.Major -eq 3) -or ($PSVersionTable.PSVersion.Major -eq 4)) + if (($PSVersionTable.PSVersion.Major -eq 3) -or ($PSVersionTable.PSVersion.Major -eq 4)) { # for PSv3 PSAvoidGlobalAliases is not shipped because # it uses StaticParameterBinder.BindCommand which is # available only on PSv4 and above - # for PowerShell Core, PSUseSingularNouns is not - # shipped because it uses APIs that are not present - # in dotnet core. $expectedNumRules-- } From 7c3862d0b1d4812e27f46bcd23d06b6a05a96c7a Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:18:05 +0000 Subject: [PATCH 21/44] fix build --- Rules/Rules.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index 61ad72071..95685a2d7 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -7,7 +7,7 @@ 1.20.0 Rules Microsoft.Windows.PowerShell.ScriptAnalyzer - PackageReference + true From c6f90fbe6d4dd7d03cdfd6ba7d3812c17d07dacc Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:24:46 +0000 Subject: [PATCH 22/44] try remove NuGet.Config that led to build failure and use same old getculture api --- Rules/UseSingularNouns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 9dfe5d82f..b34130dfd 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -55,7 +55,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) char[] funcSeperator = { '-' }; string[] funcNamePieces = new string[2]; - var usCultureInfo = new CultureInfo("en-US"); + var usCultureInfo = CultureInfo.GetCultureInfo("en-us"); #if !CORECLR var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); #else From ae7405045cc2587b5897e35939d4f83619857dc7 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 00:32:41 +0000 Subject: [PATCH 23/44] fix more tests --- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index 3a94699bd..a1b8b7380 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -270,12 +270,7 @@ Describe "Test IncludeRule" { } It "includes the given rules" { - # CoreCLR version of PSScriptAnalyzer does not contain PSUseSingularNouns rule $expectedNumViolations = 2 - if ($IsCoreCLR) - { - $expectedNumViolations = 1 - } $violations = Invoke-ScriptAnalyzer $PSScriptRoot\..\Rules\BadCmdlet.ps1 -IncludeRule $rules $violations.Count | Should -Be $expectedNumViolations } @@ -295,12 +290,7 @@ Describe "Test IncludeRule" { } It "includes 2 wildcardrules" { - # CoreCLR version of PSScriptAnalyzer does not contain PSUseSingularNouns rule $expectedNumViolations = 4 - if ($IsCoreCLR) - { - $expectedNumViolations = 3 - } $includeWildcard = Invoke-ScriptAnalyzer $PSScriptRoot\..\Rules\BadCmdlet.ps1 -IncludeRule $avoidRules $includeWildcard += Invoke-ScriptAnalyzer $PSScriptRoot\..\Rules\BadCmdlet.ps1 -IncludeRule $useRules $includeWildcard.Count | Should -Be $expectedNumViolations From 12e634f3b9ab621a4ab3b2fd655b7635fa9dd08c Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 10:28:57 +0000 Subject: [PATCH 24/44] fix build --- build.psm1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build.psm1 b/build.psm1 index 0a3906db3..0eeae95a0 100644 --- a/build.psm1 +++ b/build.psm1 @@ -311,17 +311,17 @@ function Start-ScriptAnalyzerBuild $settingsFiles = Get-Childitem "$projectRoot\Engine\Settings" | ForEach-Object -MemberName FullName Publish-File $settingsFiles (Join-Path -Path $script:destinationDir -ChildPath Settings) + $rulesProjectOutputDir = if ($env:TF_BUILD) { + "$projectRoot\bin\${buildConfiguration}\${framework}" } + else { + "$projectRoot\Rules\bin\${buildConfiguration}\${framework}" + } if ($framework -eq 'net452') { - if ( $env:TF_BUILD ) { - $nsoft = "$projectRoot\bin\${buildConfiguration}\${framework}\Newtonsoft.Json.dll" - } - else { - $nsoft = "$projectRoot\Rules\bin\${buildConfiguration}\${framework}\Newtonsoft.Json.dll" - } + $nsoft = Join-Path $rulesProjectOutputDir 'Newtonsoft.Json.dll' Copy-Item -path $nsoft -Destination $destinationDirBinaries } else { - Copy-Item -path "$projectRoot\Rules\bin\${buildConfiguration}\${framework}\PluralizeService.Core.dll" -Destination $destinationDirBinaries + Copy-Item -Path (Join-Path $rulesProjectOutputDir 'PluralizeService.Core.dll') -Destination $destinationDirBinaries } Pop-Location From c474c904b6d22528b2109f9ff24b513c04befc0c Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 23 Jan 2021 10:31:12 +0000 Subject: [PATCH 25/44] fix last 2 test failures --- Tests/Documentation/RuleDocumentation.tests.ps1 | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Tests/Documentation/RuleDocumentation.tests.ps1 b/Tests/Documentation/RuleDocumentation.tests.ps1 index 6b0d93a51..f08d9689e 100644 --- a/Tests/Documentation/RuleDocumentation.tests.ps1 +++ b/Tests/Documentation/RuleDocumentation.tests.ps1 @@ -15,14 +15,8 @@ Describe "Validate rule documentation files" { }} | Sort-Object - # Remove rules from the diff list that aren't supported on PSCore - if (($PSVersionTable.PSVersion.Major -ge 6) -and ($PSVersionTable.PSEdition -eq "Core")) - { - $RulesNotSupportedInNetstandard2 = @("PSUseSingularNouns") - $docs = $docs | Where-Object {$RulesNotSupportedInNetstandard2 -notcontains $_} - $readmeRules = $readmeRules | Where-Object { $RulesNotSupportedInNetstandard2 -notcontains $_ } - } - elseif ($PSVersionTable.PSVersion.Major -eq 4) { + # Remove rules from the diff list that aren't supported on old PS version + if ($PSVersionTable.PSVersion.Major -eq 4) { $docs = $docs | Where-Object {$_ -notmatch '^PSAvoidGlobalAliases$'} $readmeRules = $readmeRules | Where-Object { $_ -notmatch '^PSAvoidGlobalAliases$' } } From e8467d7f565ea8b61803fa809ef4d3a26c618005 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sun, 7 Feb 2021 17:01:31 +0000 Subject: [PATCH 26/44] use Pluralize.NET library, which has MIT licence --- Rules/Rules.csproj | 4 ++-- Rules/UseSingularNouns.cs | 14 ++++++-------- build.psm1 | 2 +- tools/releaseBuild/signing.xml | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index 95685a2d7..b59837c52 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -7,7 +7,7 @@ 1.20.0 Rules Microsoft.Windows.PowerShell.ScriptAnalyzer - true + true @@ -19,7 +19,7 @@ - + diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index b34130dfd..b0494e400 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -18,8 +18,7 @@ #if !CORECLR using System.ComponentModel.Composition; #else -using PluralizationService; -using PluralizationService.English; +using Pluralize.NET; #endif using System.Globalization; using System.Text.RegularExpressions; @@ -55,13 +54,12 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) char[] funcSeperator = { '-' }; string[] funcNamePieces = new string[2]; - var usCultureInfo = CultureInfo.GetCultureInfo("en-us"); + #if !CORECLR - var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); + var usCultureInfo = CultureInfo.GetCultureInfo("en-us"); + var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); #else - var pluralizationApiBuilder = new PluralizationService.PluralizationApiBuilder(); - pluralizationApiBuilder.AddEnglishProvider(); - IPluralizationApi pluralizationService = pluralizationApiBuilder.Build(); + var pluralizationService = new Pluralizer(); #endif foreach (FunctionDefinitionAst funcAst in funcAsts) @@ -84,7 +82,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) #if !CORECLR if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) #else - if (!pluralizationService.IsSingular(noun, usCultureInfo) && pluralizationService.IsPlural(noun, usCultureInfo)) + if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) #endif { IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); diff --git a/build.psm1 b/build.psm1 index 0eeae95a0..5743be761 100644 --- a/build.psm1 +++ b/build.psm1 @@ -321,7 +321,7 @@ function Start-ScriptAnalyzerBuild Copy-Item -path $nsoft -Destination $destinationDirBinaries } else { - Copy-Item -Path (Join-Path $rulesProjectOutputDir 'PluralizeService.Core.dll') -Destination $destinationDirBinaries + Copy-Item -Path (Join-Path $rulesProjectOutputDir 'Pluralize.NET.dll') -Destination $destinationDirBinaries } Pop-Location diff --git a/tools/releaseBuild/signing.xml b/tools/releaseBuild/signing.xml index 90ff91594..25c44ff89 100644 --- a/tools/releaseBuild/signing.xml +++ b/tools/releaseBuild/signing.xml @@ -24,7 +24,7 @@ - + From ade612babb2396b3f0af3d173615310d5780dc7b Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sun, 7 Feb 2021 17:02:06 +0000 Subject: [PATCH 27/44] cleanup diff --- NuGet.Config | 1 + 1 file changed, 1 insertion(+) diff --git a/NuGet.Config b/NuGet.Config index 80f5bd7fc..ddc51cc6d 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,5 +3,6 @@ + From b070fdaa46096f7e3f9ae308a10c5cbfe7f8c77e Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sun, 7 Feb 2021 17:02:34 +0000 Subject: [PATCH 28/44] whitespace --- NuGet.Config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index ddc51cc6d..acba4478b 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,6 +3,6 @@ - + From 62de2679ef8d8e3e644855c5d20b6bbc5784a3bf Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:01:56 -0700 Subject: [PATCH 29/44] Update SingularNouns rule approach --- Rules/UseSingularNouns.cs | 128 +++++++++++++------ Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 2 +- 2 files changed, 89 insertions(+), 41 deletions(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index b0494e400..0f32d05df 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -15,10 +15,10 @@ using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; -#if !CORECLR -using System.ComponentModel.Composition; -#else +#if CORECLR using Pluralize.NET; +#else +using System.ComponentModel.Composition; #endif using System.Globalization; using System.Text.RegularExpressions; @@ -55,48 +55,42 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) char[] funcSeperator = { '-' }; string[] funcNamePieces = new string[2]; -#if !CORECLR - var usCultureInfo = CultureInfo.GetCultureInfo("en-us"); - var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo); -#else - var pluralizationService = new Pluralizer(); -#endif + var pluralizer = new PluralizerProxy(); foreach (FunctionDefinitionAst funcAst in funcAsts) { - if (funcAst.Name != null && funcAst.Name.Contains('-')) + if (funcAst.Name == null || !funcAst.Name.Contains('-')) + { + continue; + } + + string noun = GetLastWordInCmdlet(funcAst.Name); + + if (noun is null) { - funcNamePieces = funcAst.Name.Split(funcSeperator); - String nounPart = funcNamePieces[1]; - - // Convert the noun part of the function into a series of space delimited words - // This helps the PluralizationService to provide an accurate determination about the plurality of the string - nounPart = SplitCamelCaseString(nounPart); - var words = nounPart.Split(new char[] { ' ' }); - var noun = words.LastOrDefault(); - if (noun == null) + continue; + } + + if (pluralizer.CanOnlyBePlural(noun)) + { + IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); + + if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) { continue; } -#if !CORECLR - if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) -#else - if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun)) -#endif + if (extent is null) { - IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); - if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - if (null == extent) - { - extent = funcAst.Extent; - } - yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsError, funcAst.Name), - extent, GetName(), DiagnosticSeverity.Warning, fileName); + extent = funcAst.Extent; } + + yield return new DiagnosticRecord( + string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsError, funcAst.Name), + extent, + GetName(), + DiagnosticSeverity.Warning, + fileName); } } @@ -155,17 +149,71 @@ public string GetSourceName() } /// - /// SplitCamelCaseString: Splits a Camel Case'd string into individual words with space delimited + /// Gets the last word in a standard syntax, CamelCase cmdlet. + /// If the cmdlet name is non-standard, returns null. /// - private string SplitCamelCaseString(string input) + private string GetLastWordInCmdlet(string cmdletName) + { + if (string.IsNullOrEmpty(cmdletName)) + { + return null; + } + + // Cmdlet doesn't use CamelCase, so assume it's something like an initialism that shouldn't be singularized + if (!char.IsLower(cmdletName[^1])) + { + return null; + } + + for (int i = cmdletName.Length - 1; i >= 0; i--) + { + if (cmdletName[i] == '-') + { + // Cmdlet name ends in '-' -- we give up + if (i == cmdletName.Length - 1) + { + return null; + } + + // Return everything after the dash + return cmdletName.Substring(i + 1); + } + + // We just changed from lower case to upper, so we have the end word + if (char.IsUpper(cmdletName[i])) + { + return cmdletName.Substring(i); + } + } + + // We shouldn't ever get here since we should always eventually hit a '-' + // But if we do, assume this isn't supported cmdlet name + return null; + } + +#if CoreCLR + private class PluralizerProxy { - if (String.IsNullOrEmpty(input)) + private readonly Pluralizer _pluralizer; + + public PluralizerProxy() { - return String.Empty; + _pluralizer = new Pluralizer(); } - return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim(); + public bool CanOnlyBePlural(string noun) => + !_pluralizer.IsSingular(noun) && _pluralizer.IsPlural(noun); } +#else + private class PluralizerProxy + { + private static readonly PluralizationService s_pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService( + CultureInfo.GetCultureInfo('en-us')); + + public bool CanOnlyBePlural(string noun) => + !s_pluralizationService.IsSingular(noun) && s_pluralizationService.IsPlural(noun); + } +#endif } } diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index df94fdc89..e5e2159bd 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -64,7 +64,7 @@ Describe "Test Name parameters" { It "get Rules with no parameters supplied" { $defaultRules = Get-ScriptAnalyzerRule $expectedNumRules = 65 - if (($PSVersionTable.PSVersion.Major -eq 3) -or ($PSVersionTable.PSVersion.Major -eq 4)) + if ($PSVersionTable.PSVersion.Major -le 4) { # for PSv3 PSAvoidGlobalAliases is not shipped because # it uses StaticParameterBinder.BindCommand which is From a4eac45f5f97ebe80b083af59689ba100cfb406b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:07:04 -0700 Subject: [PATCH 30/44] Fix dumb string characters --- Rules/UseSingularNouns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 0f32d05df..f9a0a3334 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -208,7 +208,7 @@ public bool CanOnlyBePlural(string noun) => private class PluralizerProxy { private static readonly PluralizationService s_pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService( - CultureInfo.GetCultureInfo('en-us')); + CultureInfo.GetCultureInfo("en-us")); public bool CanOnlyBePlural(string noun) => !s_pluralizationService.IsSingular(noun) && s_pluralizationService.IsPlural(noun); From 6832617014dd2252cb4f89192ad4c4ac2e5067b9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:18:34 -0700 Subject: [PATCH 31/44] Add NuGet.config back --- NuGet.Config | 1 - 1 file changed, 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index acba4478b..80f5bd7fc 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,6 +3,5 @@ - From adb808114d9122978797dbe9bf3f43c2caa9e4a1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:23:25 -0700 Subject: [PATCH 32/44] Fix namespacing --- Rules/UseSingularNouns.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index f9a0a3334..c83428b59 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -19,6 +19,7 @@ using Pluralize.NET; #else using System.ComponentModel.Composition; +using System.Data.Entity.Design.PluralizationServices; #endif using System.Globalization; using System.Text.RegularExpressions; @@ -207,7 +208,7 @@ public bool CanOnlyBePlural(string noun) => #else private class PluralizerProxy { - private static readonly PluralizationService s_pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService( + private static readonly PluralizationService s_pluralizationService = PluralizationService.CreateService( CultureInfo.GetCultureInfo("en-us")); public bool CanOnlyBePlural(string noun) => From 2372c9e2f63b6075d7a7da892501220a96f48020 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:26:15 -0700 Subject: [PATCH 33/44] Fix pragma casing --- Rules/UseSingularNouns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index c83428b59..0d827099c 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -192,7 +192,7 @@ private string GetLastWordInCmdlet(string cmdletName) return null; } -#if CoreCLR +#if CORECLR private class PluralizerProxy { private readonly Pluralizer _pluralizer; From c465363a6db2acc87a495b58bbe1ff935f45e0f4 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:29:36 -0700 Subject: [PATCH 34/44] Use old index syntax --- Rules/UseSingularNouns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 0d827099c..17d83d744 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -161,7 +161,7 @@ private string GetLastWordInCmdlet(string cmdletName) } // Cmdlet doesn't use CamelCase, so assume it's something like an initialism that shouldn't be singularized - if (!char.IsLower(cmdletName[^1])) + if (!char.IsLower(cmdletName[cmdletName.Length - 1])) { return null; } From 4e1045bab2566f3c99ba3891251d605a324d874b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:33:15 -0700 Subject: [PATCH 35/44] Change singularization for dash cases --- Rules/UseSingularNouns.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 17d83d744..d5462e658 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -170,14 +170,8 @@ private string GetLastWordInCmdlet(string cmdletName) { if (cmdletName[i] == '-') { - // Cmdlet name ends in '-' -- we give up - if (i == cmdletName.Length - 1) - { - return null; - } - - // Return everything after the dash - return cmdletName.Substring(i + 1); + // We got to the dash without seeing a CamelCase word, so nothing to singularize + return null; } // We just changed from lower case to upper, so we have the end word From d09fcadb51a5cb5413bd163c0f71eae892b8a327 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 22 Apr 2021 17:35:40 -0700 Subject: [PATCH 36/44] Remove dead variables --- Rules/UseSingularNouns.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index d5462e658..179977e73 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -53,9 +53,6 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) IEnumerable funcAsts = ast.FindAll(item => item is FunctionDefinitionAst, true); - char[] funcSeperator = { '-' }; - string[] funcNamePieces = new string[2]; - var pluralizer = new PluralizerProxy(); foreach (FunctionDefinitionAst funcAst in funcAsts) From 025d9a4f212ca2062182fcc258fc89b7c97a3b61 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 27 Apr 2021 11:53:25 -0700 Subject: [PATCH 37/44] Add corrections --- Rules/UseSingularNouns.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 179977e73..7fbe7aab6 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -22,7 +22,6 @@ using System.Data.Entity.Design.PluralizationServices; #endif using System.Globalization; -using System.Text.RegularExpressions; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules { @@ -71,13 +70,13 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (pluralizer.CanOnlyBePlural(noun)) { - IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); - if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) { continue; } + IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst); + if (extent is null) { extent = funcAst.Extent; @@ -88,7 +87,8 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) extent, GetName(), DiagnosticSeverity.Warning, - fileName); + fileName, + suggestedCorrections: new CorrectionExtent[] { GetCorrection(pluralizer, extent, noun) }); } } @@ -146,6 +146,12 @@ public string GetSourceName() return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); } + private CorrectionExtent GetCorrection(PluralizerProxy pluralizer, IScriptExtent extent, string noun) + { + string singularNoun = pluralizer.Singularize(noun); + return new CorrectionExtent(extent, singularNoun, extent.File, $"Singularized correction of '{extent.Text}'"); + } + /// /// Gets the last word in a standard syntax, CamelCase cmdlet. /// If the cmdlet name is non-standard, returns null. @@ -195,6 +201,8 @@ public PluralizerProxy() public bool CanOnlyBePlural(string noun) => !_pluralizer.IsSingular(noun) && _pluralizer.IsPlural(noun); + + public string Singularize(string noun) => _pluralizer.Singularize(noun); } #else private class PluralizerProxy @@ -204,6 +212,8 @@ private class PluralizerProxy public bool CanOnlyBePlural(string noun) => !s_pluralizationService.IsSingular(noun) && s_pluralizationService.IsPlural(noun); + + public string Singularize(string noun) => s_pluralizationService.Singularize(noun); } #endif } From 5e71bf391b574a0893e9430e6e523701e176f9bd Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 27 Apr 2021 11:58:47 -0700 Subject: [PATCH 38/44] Fix correction --- Rules/UseSingularNouns.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index 7fbe7aab6..de9264d35 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -88,7 +88,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) GetName(), DiagnosticSeverity.Warning, fileName, - suggestedCorrections: new CorrectionExtent[] { GetCorrection(pluralizer, extent, noun) }); + suggestedCorrections: new CorrectionExtent[] { GetCorrection(pluralizer, extent, funcAst.Name, noun) }); } } @@ -146,10 +146,11 @@ public string GetSourceName() return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); } - private CorrectionExtent GetCorrection(PluralizerProxy pluralizer, IScriptExtent extent, string noun) + private CorrectionExtent GetCorrection(PluralizerProxy pluralizer, IScriptExtent extent, string commandName, string noun) { string singularNoun = pluralizer.Singularize(noun); - return new CorrectionExtent(extent, singularNoun, extent.File, $"Singularized correction of '{extent.Text}'"); + string newCommandName = commandName.Substring(0, commandName.Length - noun.Length) + singularNoun; + return new CorrectionExtent(extent, newCommandName, extent.File, $"Singularized correction of '{extent.Text}'"); } /// From 5832be41ab182d5e73c4224d03de2e220095f51f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 27 Apr 2021 12:16:38 -0700 Subject: [PATCH 39/44] Add more tests --- .../UseSingularNounsReservedVerbs.tests.ps1 | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 b/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 index b1b6e7724..14900c6c5 100644 --- a/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 +++ b/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 @@ -1,4 +1,4 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. +# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. BeforeAll { @@ -51,6 +51,45 @@ Write-Output "Adding some data" $nounNoViolations.Count | Should -Be 0 } } + + Context "Inline tests" { + BeforeAll { + $testCases = @( + @{ Script = 'function Get-Horses { "horses" }'; Extent = @{ StartCol = 10; EndCol = 20 }; Correction = 'Get-Horse' } + @{ Script = 'function ConvertTo-StartingCriteria { "criteria" }'; Extent = @{ StartCol = 10; EndCol = 36 }; Correction = 'ConvertTo-StartingCriterion' } + @{ Script = 'function Invoke-Data { "data" }' } + @{ Script = 'function Get-Horse { "horses" }' } + @{ Script = 'function get-horse { "horses" }' } + @{ Script = 'function horse { "horses" }' } + ) + } + + It 'Correctly diagnoses and corrects