-
Notifications
You must be signed in to change notification settings - Fork 395
Warn when using FileRedirection operator inside if/while statements and improve new PossibleIncorrectUsageOfAssignmentOperator rule #881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 22 commits
4a0485c
2c0bc28
29c7e34
7310387
4078e41
b7ad069
8a48a62
c9d510f
fd339f6
5e8a8be
c74a746
b467d10
594414f
c4e242e
f7ce114
bc00213
c6c4f4a
691f2ce
fe30f4d
78ef4bf
675e3eb
ad008cd
d042f37
f530d75
ed95d84
bdf39ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# PossibleIncorrectUsageOfAssignmentOperator | ||
|
||
**Severity Level: Information** | ||
|
||
## Description | ||
|
||
In many programming languages, the equality operator is denoted as `==` or `=` in many programming languages, but `PowerShell` uses `-eq`. Therefore it can easily happen that the wrong operator is used unintentionally and this rule catches a few special cases where the likelihood of that is quite high. | ||
|
||
The rule looks for usages of `==` and `=` operators inside `if`, `else if`, `while` and `do-while` statements but it will not warn if any kind of command or expression is used at the right hand side as this is probably by design. | ||
|
||
## Example | ||
|
||
### Wrong | ||
|
||
```` PowerShell | ||
if ($a = $b) | ||
{ | ||
... | ||
} | ||
```` | ||
|
||
```` PowerShell | ||
if ($a == $b) | ||
{ | ||
|
||
} | ||
```` | ||
|
||
### Correct | ||
|
||
```` PowerShell | ||
if ($a -eq $b) # Compare $a with $b | ||
{ | ||
... | ||
} | ||
```` | ||
|
||
```` PowerShell | ||
if ($a = Get-Something) # Only execute action if command returns something and assign result to variable | ||
{ | ||
Do-SomethingWith $a | ||
} | ||
```` | ||
|
||
## Implicit suppresion using Clang style | ||
|
||
There are some rare cases where assignment of variable inside an if statement is by design. Instead of suppression the rule, one can also signal that assignment was intentional by wrapping the expression in extra parenthesis. An exception for this is when `$null` is used on the LHS is used because there is no use case for this. | ||
|
||
```` powershell | ||
if (($shortVariableName = $SuperLongVariableName['SpecialItem']['AnotherItem'])) | ||
{ | ||
... | ||
} | ||
```` |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# PossibleIncorrectUsageOfRedirectionOperator | ||
|
||
**Severity Level: Information** | ||
|
||
## Description | ||
|
||
In many programming languages, the comparison operator for 'greater than' is `>` but `PowerShell` uses `-gt` for it and `-ge` (greater or equal) for `>=`. Therefore it can easily happen that the wrong operator is used unintentionally and this rule catches a few special cases where the likelihood of that is quite high. | ||
|
||
The rule looks for usages of `>` or `>=` operators inside if statements because this is likely going to be unintentional usage. | ||
|
||
## Example | ||
|
||
### Wrong | ||
|
||
```` PowerShell | ||
if ($a > $b) | ||
{ | ||
... | ||
} | ||
```` | ||
|
||
### Correct | ||
|
||
```` PowerShell | ||
if ($a -eq $b) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should be -gt here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this makes more sense to someone reading the document and comparing wrong vs right. I will change that. |
||
{ | ||
... | ||
} | ||
```` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Management.Automation.Language; | ||
|
||
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer | ||
{ | ||
/// <summary> | ||
/// The idea behind clang suppresion style is to wrap a statement in extra parenthesis to implicitly suppress warnings of its content to signal that the offending operation was deliberate. | ||
/// </summary> | ||
internal static class ClangSuppresion | ||
{ | ||
/// <summary> | ||
/// The community requested an implicit suppression mechanism that follows the clang style where warnings are not issued if the expression is wrapped in extra parenthesis | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please finish the sentence with '.' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. |
||
/// See here for details: https://github.com/Microsoft/clang/blob/349091162fcf2211a2e55cf81db934978e1c4f0c/test/SemaCXX/warn-assignment-condition.cpp#L15-L18 | ||
/// </summary> | ||
/// <param name="scriptExtent"></param> | ||
/// <returns></returns> | ||
internal static bool ScriptExtendIsWrappedInParenthesis(IScriptExtent scriptExtent) | ||
{ | ||
return scriptExtent.Text.StartsWith("(") && scriptExtent.Text.EndsWith(")"); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; | ||
using System; | ||
using System.Collections.Generic; | ||
#if !CORECLR | ||
using System.ComponentModel.Composition; | ||
#endif | ||
using System.Management.Automation.Language; | ||
using System.Globalization; | ||
|
||
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules | ||
{ | ||
/// <summary> | ||
/// PossibleIncorrectUsageOfRedirectionOperator: Warn if someone uses '>' or '>=' inside an if or elseif statement because in most cases that is not the intention. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we want it to work as well for While statements as PossibleIncorrectUsageOfAssignmentOperator ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We actually do, but the comment got out of date. I will fix that. I am following the current comment style but my feeling is that the codebase has too many repeated comments. |
||
/// The origin of this rule is that people often forget that operators change when switching between different languages such as C# and PowerShell. | ||
/// </summary> | ||
#if !CORECLR | ||
[Export(typeof(IScriptRule))] | ||
#endif | ||
public class PossibleIncorrectUsageOfRedirectionOperator : AstVisitor, IScriptRule | ||
{ | ||
/// <summary> | ||
/// The idea is to get all FileRedirectionAst inside all IfStatementAst clauses. | ||
/// </summary> | ||
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) | ||
{ | ||
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); | ||
|
||
var ifStatementAsts = ast.FindAll(testAst => testAst is IfStatementAst, searchNestedScriptBlocks: true); | ||
foreach (IfStatementAst ifStatementAst in ifStatementAsts) | ||
{ | ||
foreach (var clause in ifStatementAst.Clauses) | ||
{ | ||
var fileRedirectionAst = clause.Item1.Find(testAst => testAst is FileRedirectionAst, searchNestedScriptBlocks: false) as FileRedirectionAst; | ||
if (fileRedirectionAst != null) | ||
{ | ||
yield return new DiagnosticRecord( | ||
Strings.PossibleIncorrectUsageOfRedirectionOperatorError, fileRedirectionAst.Extent, | ||
GetName(), DiagnosticSeverity.Warning, fileName); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// GetName: Retrieves the name of this rule. | ||
/// </summary> | ||
/// <returns>The name of this rule</returns> | ||
public string GetName() | ||
{ | ||
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.PossibleIncorrectUsageOfRedirectionOperatorName); | ||
} | ||
|
||
/// <summary> | ||
/// GetCommonName: Retrieves the common name of this rule. | ||
/// </summary> | ||
/// <returns>The common name of this rule</returns> | ||
public string GetCommonName() | ||
{ | ||
return string.Format(CultureInfo.CurrentCulture, Strings.PossibleIncorrectUsageOfRedirectionOperatorCommonName); | ||
} | ||
|
||
/// <summary> | ||
/// GetDescription: Retrieves the description of this rule. | ||
/// </summary> | ||
/// <returns>The description of this rule</returns> | ||
public string GetDescription() | ||
{ | ||
return string.Format(CultureInfo.CurrentCulture, Strings.PossibleIncorrectUsageOfRedirectionOperatorDescription); | ||
} | ||
|
||
/// <summary> | ||
/// GetSourceType: Retrieves the type of the rule: builtin, managed or module. | ||
/// </summary> | ||
public SourceType GetSourceType() | ||
{ | ||
return SourceType.Builtin; | ||
} | ||
|
||
/// <summary> | ||
/// GetSeverity: Retrieves the severity of the rule: error, warning of information. | ||
/// </summary> | ||
/// <returns></returns> | ||
public RuleSeverity GetSeverity() | ||
{ | ||
return RuleSeverity.Warning; | ||
} | ||
|
||
/// <summary> | ||
/// GetSourceName: Retrieves the module/assembly name the rule is from. | ||
/// </summary> | ||
public string GetSourceName() | ||
{ | ||
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still even in this case we might want to only compare the variable with the function result. Shouldn't we give some warning message as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know what you mean but it was a strong desire of the community to not see warnings when assignment is by design as this is a common use case. See referenced issue and there was also a discussion on the testing channel in Slack. I can sort of understand this fear/annoyance of people about false positives, especially given that one cannot suppress on a per-line basis in PSSA. I guess it is better to warn in most cases and help save the developer time rather than always warning and annoying the developer.