-
Notifications
You must be signed in to change notification settings - Fork 382
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add 'UseCorrectCasing' formatting rule for cmdlet/function name (#1117)
* first working prototype: Invoke-Formatter get-childitem * tweak for fully qualified cmdlets and add tests * add rule documentation and fix 3 tests as part of that * add resource strings * fix 1 test (severity) and docs test due to rule name * fix failing test using a workaround * fix tests * remove workaround in test that has been fixed upstream * cleanup and use helper method for getting cached cmdlet data
- Loading branch information
1 parent
012fd5e
commit 55d98f2
Showing
12 changed files
with
286 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 for consistency and also because 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' } | ||
} | ||
``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// 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; | ||
|
||
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules | ||
{ | ||
/// <summary> | ||
/// UseCorrectCasing: Check if cmdlet is cased correctly. | ||
/// </summary> | ||
#if !CORECLR | ||
[Export(typeof(IScriptRule))] | ||
#endif | ||
public class UseCorrectCasing : ConfigurableRule | ||
{ | ||
/// <summary> | ||
/// AnalyzeScript: Analyze the script to check if cmdlet alias is used. | ||
/// </summary> | ||
public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) | ||
{ | ||
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); | ||
|
||
IEnumerable<Ast> commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); | ||
|
||
// Iterates all CommandAsts and check the command name. | ||
foreach (CommandAst commandAst in commandAsts) | ||
{ | ||
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; | ||
} | ||
|
||
var commandInfo = Helper.Instance.GetCommandInfo(commandName); | ||
if (commandInfo == null) | ||
{ | ||
continue; | ||
} | ||
|
||
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)); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// For a command like "gci -path c:", returns the extent of "gci" in the command | ||
/// </summary> | ||
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; | ||
} | ||
|
||
private IEnumerable<CorrectionExtent> GetCorrectionExtent(CommandAst commandAst, string correctlyCaseName) | ||
{ | ||
var description = string.Format( | ||
CultureInfo.CurrentCulture, | ||
Strings.UseCorrectCasingDescription, | ||
correctlyCaseName, | ||
correctlyCaseName); | ||
var cmdExtent = GetCommandExtent(commandAst); | ||
var correction = new CorrectionExtent( | ||
cmdExtent.StartLineNumber, | ||
cmdExtent.EndLineNumber, | ||
cmdExtent.StartColumnNumber, | ||
cmdExtent.EndColumnNumber, | ||
correctlyCaseName, | ||
commandAst.Extent.File, | ||
description); | ||
yield return correction; | ||
} | ||
|
||
/// <summary> | ||
/// GetName: Retrieves the name of this rule. | ||
/// </summary> | ||
/// <returns>The name of this rule</returns> | ||
public override string GetName() | ||
{ | ||
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseCorrectCasingName); | ||
} | ||
|
||
/// <summary> | ||
/// GetCommonName: Retrieves the common name of this rule. | ||
/// </summary> | ||
/// <returns>The common name of this rule</returns> | ||
public override string GetCommonName() | ||
{ | ||
return string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingCommonName); | ||
} | ||
|
||
/// <summary> | ||
/// GetDescription: Retrieves the description of this rule. | ||
/// </summary> | ||
/// <returns>The description of this rule</returns> | ||
public override string GetDescription() | ||
{ | ||
return string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingDescription); | ||
} | ||
|
||
/// <summary> | ||
/// GetSourceType: Retrieves the type of the rule, Builtin, Managed or Module. | ||
/// </summary> | ||
public override SourceType GetSourceType() | ||
{ | ||
return SourceType.Builtin; | ||
} | ||
|
||
/// <summary> | ||
/// GetSeverity: Retrieves the severity of the rule: error, warning of information. | ||
/// </summary> | ||
/// <returns></returns> | ||
public override RuleSeverity GetSeverity() | ||
{ | ||
return RuleSeverity.Information; | ||
} | ||
|
||
/// <summary> | ||
/// GetSourceName: Retrieves the name of the module/assembly the rule is from. | ||
/// </summary> | ||
public override string GetSourceName() | ||
{ | ||
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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' | ||
} | ||
} |