Skip to content

Commit e7e01b9

Browse files
author
Kapil Borle
authored
Merge pull request #820 from dlwyatt/AvoidTrailingWhitespace
Add PSAvoidTrailingWhitespace rule
2 parents 02f3bfd + 5480c14 commit e7e01b9

7 files changed

+223
-10
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# AvoidTrailingWhitespace
2+
3+
**Severity Level: Information**
4+
5+
## Description
6+
7+
Lines should not end with whitespace characters. This can cause problems with the line-continuation backtick, and also clutters up future commits to source control.

Rules/AvoidTrailingWhitespace.cs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright (c) Microsoft Corporation.
2+
//
3+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
4+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
5+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
6+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
7+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
8+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
9+
// THE SOFTWARE.
10+
11+
using System;
12+
using System.Collections.Generic;
13+
using System.Text.RegularExpressions;
14+
#if !CORECLR
15+
using System.ComponentModel.Composition;
16+
#endif
17+
using System.Globalization;
18+
using System.Linq;
19+
using System.Management.Automation.Language;
20+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
21+
22+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
23+
{
24+
/// <summary>
25+
/// A class to walk an AST to check for violation.
26+
/// </summary>
27+
#if !CORECLR
28+
[Export(typeof(IScriptRule))]
29+
#endif
30+
public class AvoidTrailingWhitespace : IScriptRule
31+
{
32+
/// <summary>
33+
/// Analyzes the given ast to find violations.
34+
/// </summary>
35+
/// <param name="ast">AST to be analyzed. This should be non-null</param>
36+
/// <param name="fileName">Name of file that corresponds to the input AST.</param>
37+
/// <returns>A an enumerable type containing the violations</returns>
38+
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
39+
{
40+
if (ast == null)
41+
{
42+
throw new ArgumentNullException("ast");
43+
}
44+
45+
var diagnosticRecords = new List<DiagnosticRecord>();
46+
47+
string[] lines = Regex.Split(ast.Extent.Text, @"\r?\n");
48+
49+
for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++)
50+
{
51+
var line = lines[lineNumber];
52+
53+
var match = Regex.Match(line, @"\s+$");
54+
if (match.Success)
55+
{
56+
var startLine = lineNumber + 1;
57+
var endLine = startLine;
58+
var startColumn = match.Index + 1;
59+
var endColumn = startColumn + match.Length;
60+
61+
var violationExtent = new ScriptExtent(
62+
new ScriptPosition(
63+
ast.Extent.File,
64+
startLine,
65+
startColumn,
66+
line
67+
),
68+
new ScriptPosition(
69+
ast.Extent.File,
70+
endLine,
71+
endColumn,
72+
line
73+
));
74+
75+
var suggestedCorrections = new List<CorrectionExtent>();
76+
suggestedCorrections.Add(new CorrectionExtent(
77+
violationExtent,
78+
string.Empty,
79+
ast.Extent.File
80+
));
81+
82+
diagnosticRecords.Add(
83+
new DiagnosticRecord(
84+
String.Format(CultureInfo.CurrentCulture, Strings.AvoidTrailingWhitespaceError),
85+
violationExtent,
86+
GetName(),
87+
GetDiagnosticSeverity(),
88+
ast.Extent.File,
89+
null,
90+
suggestedCorrections
91+
));
92+
}
93+
}
94+
95+
return diagnosticRecords;
96+
}
97+
98+
/// <summary>
99+
/// Retrieves the common name of this rule.
100+
/// </summary>
101+
public string GetCommonName()
102+
{
103+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidTrailingWhitespaceCommonName);
104+
}
105+
106+
/// <summary>
107+
/// Retrieves the description of this rule.
108+
/// </summary>
109+
public string GetDescription()
110+
{
111+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidTrailingWhitespaceDescription);
112+
}
113+
114+
/// <summary>
115+
/// Retrieves the name of this rule.
116+
/// </summary>
117+
public string GetName()
118+
{
119+
return string.Format(
120+
CultureInfo.CurrentCulture,
121+
Strings.NameSpaceFormat,
122+
GetSourceName(),
123+
Strings.AvoidTrailingWhitespaceName);
124+
}
125+
126+
/// <summary>
127+
/// Retrieves the severity of the rule: error, warning or information.
128+
/// </summary>
129+
public RuleSeverity GetSeverity()
130+
{
131+
return RuleSeverity.Information;
132+
}
133+
134+
/// <summary>
135+
/// Gets the severity of the returned diagnostic record: error, warning, or information.
136+
/// </summary>
137+
/// <returns></returns>
138+
public DiagnosticSeverity GetDiagnosticSeverity()
139+
{
140+
return DiagnosticSeverity.Information;
141+
}
142+
143+
/// <summary>
144+
/// Retrieves the name of the module/assembly the rule is from.
145+
/// </summary>
146+
public string GetSourceName()
147+
{
148+
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
149+
}
150+
151+
/// <summary>
152+
/// Retrieves the type of the rule, Builtin, Managed or Module.
153+
/// </summary>
154+
public SourceType GetSourceType()
155+
{
156+
return SourceType.Builtin;
157+
}
158+
}
159+
}

Rules/Strings.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,18 @@
870870
<data name="AvoidGlobalAliasesName" xml:space="preserve">
871871
<value>AvoidGlobalAliases</value>
872872
</data>
873+
<data name="AvoidTrailingWhitespaceName" xml:space="preserve">
874+
<value>AvoidTrailingWhitespace</value>
875+
</data>
876+
<data name="AvoidTrailingWhitespaceCommonName" xml:space="preserve">
877+
<value>Avoid trailing whitespace</value>
878+
</data>
879+
<data name="AvoidTrailingWhitespaceDescription" xml:space="preserve">
880+
<value>Each line should have no trailing whitespace.</value>
881+
</data>
882+
<data name="AvoidTrailingWhitespaceError" xml:space="preserve">
883+
<value>Line has trailing whitespace</value>
884+
</data>
873885
<data name="PlaceOpenBraceName" xml:space="preserve">
874886
<value>PlaceOpenBrace</value>
875887
</data>

Tests/Engine/GetScriptAnalyzerRule.tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Describe "Test Name parameters" {
6161

6262
It "get Rules with no parameters supplied" {
6363
$defaultRules = Get-ScriptAnalyzerRule
64-
$expectedNumRules = 51
64+
$expectedNumRules = 52
6565
if ((Test-PSEditionCoreClr) -or (Test-PSVersionV3) -or (Test-PSVersionV4))
6666
{
6767
# for PSv3 PSAvoidGlobalAliases is not shipped because
@@ -159,7 +159,7 @@ Describe "TestSeverity" {
159159

160160
It "filters rules based on multiple severity inputs"{
161161
$rules = Get-ScriptAnalyzerRule -Severity Error,Information
162-
$rules.Count | Should be 13
162+
$rules.Count | Should be 14
163163
}
164164

165165
It "takes lower case inputs" {

Tests/Rules/AvoidDefaultValueForMandatoryParameterNoViolations.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
{
33
param(
44
[Parameter(Mandatory=$true)]
5-
[ValidateNotNullOrEmpty()]
5+
[ValidateNotNullOrEmpty()]
66
[string]
77
$Param1,
88
[Parameter(Mandatory=$false)]
9-
[ValidateNotNullOrEmpty()]
9+
[ValidateNotNullOrEmpty()]
1010
[string]
1111
$Param2=$null
1212
)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
$directory = Split-Path -Parent $MyInvocation.MyCommand.Path
2+
$testRootDirectory = Split-Path -Parent $directory
3+
4+
Import-Module PSScriptAnalyzer
5+
Import-Module (Join-Path $testRootDirectory "PSScriptAnalyzerTestHelper.psm1")
6+
7+
$ruleName = "PSAvoidTrailingWhitespace"
8+
9+
$settings = @{
10+
IncludeRules = @($ruleName)
11+
}
12+
13+
Describe "AvoidTrailingWhitespace" {
14+
$testCases = @(
15+
@{
16+
Type = 'spaces'
17+
Whitespace = ' '
18+
}
19+
20+
@{
21+
Type = 'tabs'
22+
Whitespace = "`t`t`t"
23+
}
24+
)
25+
26+
It 'Should find a violation when a line contains trailing <Type>' -TestCases $testCases {
27+
param (
28+
[string] $Whitespace
29+
)
30+
31+
$def = "`$null = `$null$Whitespace"
32+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
33+
Test-CorrectionExtentFromContent $def $violations 1 $Whitespace ''
34+
}
35+
}

Tests/Rules/BadCmdlet.ps1

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
function Verb-Files
22
{
3-
[CmdletBinding(DefaultParameterSetName='Parameter Set 1',
4-
SupportsShouldProcess=$true,
3+
[CmdletBinding(DefaultParameterSetName='Parameter Set 1',
4+
SupportsShouldProcess=$true,
55
PositionalBinding=$false,
66
HelpUri = 'http://www.microsoft.com/',
77
ConfirmImpact='Medium')]
@@ -12,17 +12,17 @@
1212
Param
1313
(
1414
# Param1 help description
15-
[Parameter(Mandatory=$true,
15+
[Parameter(Mandatory=$true,
1616
ValueFromPipeline=$true,
17-
ValueFromPipelineByPropertyName=$true,
18-
ValueFromRemainingArguments=$false,
17+
ValueFromPipelineByPropertyName=$true,
18+
ValueFromRemainingArguments=$false,
1919
Position=0,
2020
ParameterSetName='Parameter Set 1')]
2121
[ValidateNotNull()]
2222
[ValidateNotNullOrEmpty()]
2323
[ValidateCount(0,5)]
2424
[ValidateSet("sun", "moon", "earth")]
25-
[Alias("p1")]
25+
[Alias("p1")]
2626
$Param1,
2727

2828
# Param2 help description

0 commit comments

Comments
 (0)