Skip to content

Commit fcd9a4e

Browse files
author
Kapil Borle
authored
Merge pull request #690 from PowerShell/kapilmb/CodeFormatting
Add code formatting rules
2 parents db61dd6 + c65ff01 commit fcd9a4e

20 files changed

+1758
-36
lines changed

Engine/Generic/ConfigurableRule.cs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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.Management.Automation.Language;
14+
using System.Reflection;
15+
16+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic
17+
{
18+
// This is still an experimental class. Use at your own risk!
19+
public abstract class ConfigurableRule : IScriptRule
20+
{
21+
/// <summary>
22+
/// Indicates if the rule is enabled or not.
23+
///
24+
/// If the rule is enabled ScriptAnalyzer engine will run it
25+
/// otherwise it will not.
26+
///
27+
/// Configurable rule properties should define a default value
28+
/// because if reading the configuration fails we use the
29+
/// property's default value
30+
/// </summary>
31+
[ConfigurableRuleProperty(defaultValue: false)]
32+
public bool Enable { get; protected set; }
33+
34+
/// <summary>
35+
/// Initialize the configurable properties of a configurable rule.
36+
/// </summary>
37+
protected ConfigurableRule()
38+
{
39+
SetDefaultValues();
40+
}
41+
42+
/// <summary>
43+
/// Sets the configurable properties of the rule.
44+
///
45+
/// Properties having ConfigurableRuleProperty attribute are called configurable properties.
46+
/// </summary>
47+
/// <param name="paramValueMap">A dictionary that maps parameter name to it value. Must be non-null</param>
48+
public virtual void ConfigureRule(IDictionary<string, object> paramValueMap)
49+
{
50+
if (paramValueMap == null)
51+
{
52+
throw new ArgumentNullException(nameof(paramValueMap));
53+
}
54+
55+
try
56+
{
57+
foreach (var property in GetConfigurableProperties())
58+
{
59+
if (paramValueMap.ContainsKey(property.Name))
60+
{
61+
SetValue(property, paramValueMap[property.Name]);
62+
}
63+
}
64+
}
65+
catch
66+
{
67+
// we do not know how to handle an exception in this case yet!
68+
// but we know that this should not crash the program
69+
// hence we revert the property values to their default
70+
SetDefaultValues();
71+
}
72+
}
73+
74+
/// <summary>
75+
/// Analyzes the given abstract syntax tree (AST) and returns diagnostic records based on the analysis.
76+
/// </summary>
77+
/// <param name="ast">AST representing the file content</param>
78+
/// <param name="fileName">Path of the file corresponding to the AST</param>
79+
/// <returns>The results of the analysis</returns>
80+
public abstract IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName);
81+
82+
/// <summary>
83+
/// Retrieves the Common name of the rule.
84+
/// </summary>
85+
/// <returns>The name of the rule.</returns>
86+
public abstract string GetCommonName();
87+
88+
/// <summary>
89+
/// Retrieves the description of the rule.
90+
/// </summary>
91+
/// <returns>The description of the rule.</returns>
92+
public abstract string GetDescription();
93+
94+
/// <summary>
95+
/// Retrieves the name of the rule.
96+
/// </summary>
97+
/// <returns>The name of the rule.</returns>
98+
public abstract string GetName();
99+
100+
/// <summary>
101+
/// Retrieves severity of the rule.
102+
/// </summary>
103+
/// <returns>The severity of the rule.</returns>
104+
public abstract RuleSeverity GetSeverity();
105+
106+
/// <summary>
107+
/// Retrieves the source name of the rule.
108+
/// </summary>
109+
/// <returns>The source name of the rule.</returns>
110+
public abstract string GetSourceName();
111+
112+
/// <summary>
113+
/// Retrieves the source type of the rule.
114+
/// </summary>
115+
/// <returns>The source type of the rule.</returns>
116+
public abstract SourceType GetSourceType();
117+
118+
private void SetDefaultValues()
119+
{
120+
foreach (var property in GetConfigurableProperties())
121+
{
122+
SetValue(property, GetDefaultValue(property));
123+
}
124+
}
125+
126+
private void SetValue(PropertyInfo property, object value)
127+
{
128+
// TODO Check if type is convertible
129+
property.SetValue(this, Convert.ChangeType(value, property.PropertyType));
130+
}
131+
132+
private IEnumerable<PropertyInfo> GetConfigurableProperties()
133+
{
134+
foreach (var property in this.GetType().GetProperties())
135+
{
136+
if (property.GetCustomAttribute(typeof(ConfigurableRulePropertyAttribute)) != null)
137+
{
138+
yield return property;
139+
}
140+
}
141+
}
142+
143+
private Object GetDefaultValue(PropertyInfo property)
144+
{
145+
var attr = property.GetCustomAttribute(typeof(ConfigurableRulePropertyAttribute));
146+
if (attr == null)
147+
{
148+
throw new ArgumentException(
149+
String.Format(Strings.ConfigurableScriptRulePropertyHasNotAttribute, property.Name),
150+
nameof(property));
151+
}
152+
153+
return ((ConfigurableRulePropertyAttribute)attr).DefaultValue;
154+
}
155+
}
156+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
13+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic
14+
{
15+
/// <summary>
16+
/// The attribute class to designate if a property is configurable or not.
17+
/// </summary>
18+
[AttributeUsage(AttributeTargets.Property)]
19+
public class ConfigurableRulePropertyAttribute : Attribute
20+
{
21+
/// <summary>
22+
/// Default value of the property that the attribute decorates.
23+
/// </summary>
24+
public object DefaultValue { get; private set; }
25+
26+
/// <summary>
27+
/// Initialize the attribute with the decorated property's default value.
28+
/// </summary>
29+
/// <param name="defaultValue"></param>
30+
public ConfigurableRulePropertyAttribute(object defaultValue)
31+
{
32+
if (defaultValue == null)
33+
{
34+
throw new ArgumentNullException(nameof(defaultValue), Strings.ConfigurableScriptRuleNRE);
35+
}
36+
37+
DefaultValue = defaultValue;
38+
}
39+
}
40+
}

Engine/Helper.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ internal set
6464
{
6565
lock (syncRoot)
6666
{
67-
if (instance == null)
68-
{
69-
instance = value;
70-
}
67+
instance = value;
7168
}
7269
}
7370
}

Engine/ScriptAnalyzer.cs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
//
1313

1414
using System.Text.RegularExpressions;
15-
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands;
1615
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
1716
using System;
1817
using System.Collections.Generic;
@@ -490,7 +489,10 @@ private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutp
490489
{
491490
return hasError;
492491
}
493-
foreach (var settingKey in settings.Keys)
492+
493+
var settingsKeys = new string[settings.Keys.Count];
494+
settings.Keys.CopyTo(settingsKeys, 0);
495+
foreach (var settingKey in settingsKeys)
494496
{
495497
var key = settingKey.ToLower();
496498
object value = settings[key];
@@ -599,6 +601,7 @@ private bool ParseProfileString(string profile, PathIntrinsics path, IOutputWrit
599601
else
600602
{
601603
HashtableAst hashTableAst = hashTableAsts.First() as HashtableAst;
604+
#if PSV3
602605
settings = GetDictionaryFromHashTableAst(
603606
hashTableAst,
604607
writer,
@@ -627,6 +630,24 @@ private bool ParseProfileString(string profile, PathIntrinsics path, IOutputWrit
627630
hasError = true;
628631
}
629632
}
633+
#else
634+
635+
try
636+
{
637+
var hashtable = hashTableAst.SafeGetValue() as Hashtable;
638+
hasError = ParseProfileHashtable(hashtable, path, writer, severityList, includeRuleList, excludeRuleList);
639+
}
640+
catch
641+
{
642+
writer.WriteError(
643+
new ErrorRecord(
644+
new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.InvalidProfile, profile)),
645+
Strings.ConfigurationFileHasInvalidHashtable,
646+
ErrorCategory.ResourceUnavailable,
647+
profile));
648+
hasError = true;
649+
}
650+
#endif // PSV3
630651
}
631652
}
632653

@@ -754,6 +775,7 @@ private void Initialize(
754775
try
755776
{
756777
this.LoadRules(this.validationResults, invokeCommand, includeDefaultRules);
778+
this.ConfigureScriptRules();
757779
}
758780
catch (Exception ex)
759781
{
@@ -942,6 +964,32 @@ private void LoadRules(Dictionary<string, List<string>> result, CommandInvocatio
942964
}
943965
}
944966

967+
// Configure rules derived from ConfigurableScriptRule class
968+
private void ConfigureScriptRules()
969+
{
970+
if (ScriptRules == null)
971+
{
972+
return;
973+
}
974+
975+
foreach (var scriptRule in ScriptRules)
976+
{
977+
var configurableScriptRule = scriptRule as ConfigurableRule;
978+
if (configurableScriptRule == null)
979+
{
980+
continue;
981+
}
982+
983+
var paramValueMap = Helper.Instance.GetRuleArguments(scriptRule.GetName());
984+
if (paramValueMap == null)
985+
{
986+
continue;
987+
}
988+
989+
configurableScriptRule.ConfigureRule(paramValueMap);
990+
}
991+
}
992+
945993
internal string[] GetValidModulePaths()
946994
{
947995
List<string> validModulePaths = null;
@@ -1621,6 +1669,12 @@ private bool IsSeverityAllowed(IEnumerable<uint> allowedSeverities, IRule rule)
16211669
&& allowedSeverities.Contains((uint)rule.GetSeverity()));
16221670
}
16231671

1672+
private static bool IsRuleEnabled(IRule rule)
1673+
{
1674+
var configurableRule = rule as ConfigurableRule;
1675+
return configurableRule == null || configurableRule.Enable;
1676+
}
1677+
16241678
IEnumerable<uint> GetAllowedSeveritiesInInt()
16251679
{
16261680
return severity != null
@@ -1667,7 +1721,8 @@ bool IsRuleAllowed(IRule rule)
16671721

16681722
return (includeRule == null || includeRegexMatch)
16691723
&& (excludeRule == null || !excludeRegexMatch)
1670-
&& IsSeverityAllowed(allowedSeverities, rule);
1724+
&& IsSeverityAllowed(allowedSeverities, rule)
1725+
&& IsRuleEnabled(rule);
16711726
}
16721727

16731728
/// <summary>

Engine/ScriptAnalyzerEngine.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
<Compile Include="Commands\InvokeScriptAnalyzerCommand.cs" />
7171
<Compile Include="Generic\AvoidCmdletGeneric.cs" />
7272
<Compile Include="Generic\AvoidParameterGeneric.cs" />
73+
<Compile Include="Generic\ConfigurableRulePropertyAttribute.cs" />
74+
<Compile Include="Generic\ConfigurableRule.cs" />
7375
<Compile Include="Generic\ModuleDependencyHandler.cs" />
7476
<Compile Include="Generic\CorrectionExtent.cs" />
7577
<Compile Include="Generic\SuppressedRecord.cs" />
@@ -99,6 +101,7 @@
99101
<DesignTime>True</DesignTime>
100102
<DependentUpon>Strings.resx</DependentUpon>
101103
</Compile>
104+
<Compile Include="TokenOperations.cs" />
102105
<Compile Include="VariableAnalysis.cs" />
103106
<Compile Include="VariableAnalysisBase.cs" />
104107
</ItemGroup>

Engine/Settings/CodeFormatting.psd1

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@{
2+
IncludeRules = @(
3+
'PSPlaceOpenBrace',
4+
'PSPlaceCloseBrace',
5+
'PSUseConsistentIndentation'
6+
)
7+
8+
Rules = @{
9+
PSPlaceOpenBrace = @{
10+
Enable = $true
11+
OnSameLine = $true
12+
NewLineAfter = $true
13+
}
14+
15+
PSPlaceCloseBrace = @{
16+
Enable = $true
17+
}
18+
19+
PSUseConsistentIndentation = @{
20+
Enable = $true
21+
IndentationSize = 4
22+
}
23+
}
24+
}

Engine/Strings.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@
216216
<data name="VerboseScriptDefinitionMessage" xml:space="preserve">
217217
<value>Analyzing Script Definition.</value>
218218
</data>
219+
<data name="ConfigurationFileHasInvalidHashtable" xml:space="preserve">
220+
<value>SettingsFileHasInvalidHashtable</value>
221+
</data>
219222
<data name="ConfigurationFileHasNoHashTable" xml:space="preserve">
220223
<value>SettingsFileHasNoHashTable</value>
221224
</data>
@@ -252,4 +255,10 @@
252255
<data name="DigraphVertexDoesNotExists" xml:space="preserve">
253256
<value>Vertex {0} does not exist in the digraph.</value>
254257
</data>
258+
<data name="ConfigurableScriptRulePropertyHasNotAttribute" xml:space="preserve">
259+
<value>"Cannot find a ConfigurableRuleProperty attribute on property {0}".</value>
260+
</data>
261+
<data name="ConfigurableScriptRuleNRE" xml:space="preserve">
262+
<value>"Argument should not be null.".</value>
263+
</data>
255264
</root>

0 commit comments

Comments
 (0)