Skip to content

Commit 9ee8c0f

Browse files
committed
Implement PS script rules
- Implement a Write-Diagnostic cmdlet for scripts to use - Add a runspace pool for running script rules - Add picking up of modules from configured paths to run as rules - Use a provider factory for instantiation to make dependencies easier - Fix various configuration bugs
1 parent 20f3acf commit 9ee8c0f

25 files changed

+395
-203
lines changed

ScriptAnalyzer2.sln

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.26124.0
5+
MinimumVisualStudioVersion = 15.0.26124.0
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptAnalyzer2", "ScriptAnalyzer2\ScriptAnalyzer2.csproj", "{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{42FA7E22-CFD2-46D4-9371-E4D5BB5AF2D2}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptAnalyzer2.Test", "Tests\ScriptAnalyzer2.Test\ScriptAnalyzer2.Test.csproj", "{57802860-8DC7-4360-A368-E3EE02A1C16B}"
11+
EndProject
12+
Global
13+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
14+
Debug|Any CPU = Debug|Any CPU
15+
Debug|x64 = Debug|x64
16+
Debug|x86 = Debug|x86
17+
Release|Any CPU = Release|Any CPU
18+
Release|x64 = Release|x64
19+
Release|x86 = Release|x86
20+
EndGlobalSection
21+
GlobalSection(SolutionProperties) = preSolution
22+
HideSolutionNode = FALSE
23+
EndGlobalSection
24+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
25+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
27+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Debug|x64.ActiveCfg = Debug|Any CPU
28+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Debug|x64.Build.0 = Debug|Any CPU
29+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Debug|x86.ActiveCfg = Debug|Any CPU
30+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Debug|x86.Build.0 = Debug|Any CPU
31+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
32+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Release|Any CPU.Build.0 = Release|Any CPU
33+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Release|x64.ActiveCfg = Release|Any CPU
34+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Release|x64.Build.0 = Release|Any CPU
35+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Release|x86.ActiveCfg = Release|Any CPU
36+
{C46DA142-F6F8-4C83-89ED-40AF9F4262AE}.Release|x86.Build.0 = Release|Any CPU
37+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Debug|Any CPU.Build.0 = Debug|Any CPU
39+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Debug|x64.ActiveCfg = Debug|Any CPU
40+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Debug|x64.Build.0 = Debug|Any CPU
41+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Debug|x86.ActiveCfg = Debug|Any CPU
42+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Debug|x86.Build.0 = Debug|Any CPU
43+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Release|Any CPU.ActiveCfg = Release|Any CPU
44+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Release|Any CPU.Build.0 = Release|Any CPU
45+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Release|x64.ActiveCfg = Release|Any CPU
46+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Release|x64.Build.0 = Release|Any CPU
47+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Release|x86.ActiveCfg = Release|Any CPU
48+
{57802860-8DC7-4360-A368-E3EE02A1C16B}.Release|x86.Build.0 = Release|Any CPU
49+
EndGlobalSection
50+
GlobalSection(NestedProjects) = preSolution
51+
{57802860-8DC7-4360-A368-E3EE02A1C16B} = {42FA7E22-CFD2-46D4-9371-E4D5BB5AF2D2}
52+
EndGlobalSection
53+
EndGlobal

ScriptAnalyzer2/Builder/BuiltinRulesBuilder.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ public class BuiltinRulesBuilder
1010
{
1111
private IReadOnlyDictionary<string, IRuleConfiguration> _ruleConfiguration;
1212

13-
private IRuleComponentProvider _ruleComponents;
13+
private RuleComponentProvider _ruleComponents;
1414

1515
public BuiltinRulesBuilder WithRuleConfiguration(IReadOnlyDictionary<string, IRuleConfiguration> ruleConfigurationCollection)
1616
{
1717
_ruleConfiguration = ruleConfigurationCollection;
1818
return this;
1919
}
2020

21-
public BuiltinRulesBuilder WithRuleComponentProvider(IRuleComponentProvider ruleComponentProvider)
21+
public BuiltinRulesBuilder WithRuleComponentProvider(RuleComponentProvider ruleComponentProvider)
2222
{
2323
_ruleComponents = ruleComponentProvider;
2424
return this;
@@ -32,12 +32,9 @@ public BuiltinRulesBuilder WithRuleComponentBuilder(Action<RuleComponentProvider
3232
return this;
3333
}
3434

35-
public TypeRuleProvider Build()
35+
public IRuleProviderFactory Build()
3636
{
37-
return TypeRuleProvider.FromTypes(
38-
_ruleConfiguration ?? Default.RuleConfiguration,
39-
_ruleComponents ?? Default.RuleComponentProvider,
40-
BuiltinRules.DefaultRules);
37+
return new BuiltinRuleProviderFactory(_ruleConfiguration);
4138
}
4239
}
4340
}

ScriptAnalyzer2/Builder/ConfiguredBuilding.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ public static class ConfiguredBuilding
1313
{
1414
public static ScriptAnalyzer CreateScriptAnalyzer(this IScriptAnalyzerConfiguration configuration)
1515
{
16-
var analyzerBuilder = new ScriptAnalyzerBuilder();
16+
var analyzerBuilder = new ScriptAnalyzerBuilder()
17+
.WithRuleComponentProvider(new RuleComponentProviderBuilder().Build());
1718

1819
switch (configuration.BuiltinRules ?? BuiltinRulePreference.Default)
1920
{
@@ -35,8 +36,6 @@ public static ScriptAnalyzer CreateScriptAnalyzer(this IScriptAnalyzerConfigurat
3536
break;
3637
}
3738

38-
var componentProvider = new RuleComponentProviderBuilder().Build();
39-
4039
if (configuration.RulePaths != null)
4140
{
4241
foreach (string rulePath in configuration.RulePaths)
@@ -45,7 +44,7 @@ public static ScriptAnalyzer CreateScriptAnalyzer(this IScriptAnalyzerConfigurat
4544

4645
if (extension.CaseInsensitiveEquals(".dll"))
4746
{
48-
analyzerBuilder.AddRuleProvider(TypeRuleProvider.FromAssemblyFile(configuration.RuleConfiguration, componentProvider, rulePath));
47+
analyzerBuilder.AddRuleProviderFactory(TypeRuleProviderFactory.FromAssemblyFile(configuration.RuleConfiguration, rulePath));
4948
break;
5049
}
5150
}

ScriptAnalyzer2/Builder/ScriptAnalyzerBuilder.cs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ namespace Microsoft.PowerShell.ScriptAnalyzer.Builder
88
{
99
public class ScriptAnalyzerBuilder
1010
{
11-
private readonly List<IRuleProvider> _ruleProviders;
11+
private readonly List<IRuleProviderFactory> _ruleProviderFactories;
1212

1313
private IRuleExecutorFactory _ruleExecutorFactory;
1414

15+
private RuleComponentProvider _ruleComponentProvider;
16+
1517
public ScriptAnalyzerBuilder()
1618
{
17-
_ruleProviders = new List<IRuleProvider>();
19+
_ruleProviderFactories = new List<IRuleProviderFactory>();
1820
}
1921

2022
public ScriptAnalyzerBuilder WithRuleExecutorFactory(IRuleExecutorFactory ruleExecutorFactory)
@@ -23,36 +25,44 @@ public ScriptAnalyzerBuilder WithRuleExecutorFactory(IRuleExecutorFactory ruleEx
2325
return this;
2426
}
2527

26-
public ScriptAnalyzerBuilder AddRuleProvider(IRuleProvider ruleProvider)
28+
public ScriptAnalyzerBuilder WithRuleComponentProvider(RuleComponentProvider ruleComponentProvider)
29+
{
30+
_ruleComponentProvider = ruleComponentProvider;
31+
return this;
32+
}
33+
34+
public ScriptAnalyzerBuilder WithRuleComponentProvider(Action<RuleComponentProviderBuilder> configureComponentProviderBuilder)
2735
{
28-
_ruleProviders.Add(ruleProvider);
36+
var componentProviderBuilder = new RuleComponentProviderBuilder();
37+
configureComponentProviderBuilder(componentProviderBuilder);
38+
WithRuleComponentProvider(componentProviderBuilder.Build());
39+
return this;
40+
}
41+
42+
public ScriptAnalyzerBuilder AddRuleProviderFactory(IRuleProviderFactory ruleProvider)
43+
{
44+
_ruleProviderFactories.Add(ruleProvider);
2945
return this;
3046
}
3147

3248
public ScriptAnalyzerBuilder AddBuiltinRules()
3349
{
34-
_ruleProviders.Add(TypeRuleProvider.FromTypes(
35-
Default.RuleConfiguration,
36-
Default.RuleComponentProvider,
37-
BuiltinRules.DefaultRules));
50+
_ruleProviderFactories.Add(
51+
new BuiltinRuleProviderFactory(Default.RuleConfiguration));
3852
return this;
3953
}
4054

4155
public ScriptAnalyzerBuilder AddBuiltinRules(Action<BuiltinRulesBuilder> configureBuiltinRules)
4256
{
4357
var builtinRulesBuilder = new BuiltinRulesBuilder();
4458
configureBuiltinRules(builtinRulesBuilder);
45-
_ruleProviders.Add(builtinRulesBuilder.Build());
59+
_ruleProviderFactories.Add(builtinRulesBuilder.Build());
4660
return this;
4761
}
4862

4963
public ScriptAnalyzer Build()
5064
{
51-
IRuleProvider ruleProvider = _ruleProviders.Count == 1
52-
? _ruleProviders[0]
53-
: new CompositeRuleProvider(_ruleProviders);
54-
55-
return new ScriptAnalyzer(ruleProvider, _ruleExecutorFactory ?? Default.RuleExecutorFactory);
65+
return ScriptAnalyzer.Create(_ruleComponentProvider, _ruleExecutorFactory, _ruleProviderFactories);
5666
}
5767
}
5868
}

ScriptAnalyzer2/Builtin/BuiltinRuleProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static class BuiltinRules
2525

2626
public static class Default
2727
{
28-
private static readonly Lazy<IRuleComponentProvider> s_ruleComponentProviderLazy = new Lazy<IRuleComponentProvider>(BuildRuleComponentProvider);
28+
private static readonly Lazy<RuleComponentProvider> s_ruleComponentProviderLazy = new Lazy<RuleComponentProvider>(BuildRuleComponentProvider);
2929

3030
public static IReadOnlyDictionary<string, IRuleConfiguration> RuleConfiguration { get; } = new Dictionary<string, IRuleConfiguration>(StringComparer.OrdinalIgnoreCase)
3131
{
@@ -39,9 +39,9 @@ public static class Default
3939

4040
public static IRuleExecutorFactory RuleExecutorFactory { get; } = new ParallelLinqRuleExecutorFactory();
4141

42-
public static IRuleComponentProvider RuleComponentProvider => s_ruleComponentProviderLazy.Value;
42+
public static RuleComponentProvider RuleComponentProvider => s_ruleComponentProviderLazy.Value;
4343

44-
private static IRuleComponentProvider BuildRuleComponentProvider()
44+
private static RuleComponentProvider BuildRuleComponentProvider()
4545
{
4646
return new RuleComponentProviderBuilder()
4747
.AddSingleton(InstantiatePowerShellCommandDatabase())
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Microsoft.PowerShell.ScriptAnalyzer.Builder;
2+
using Microsoft.PowerShell.ScriptAnalyzer.Configuration;
3+
using Microsoft.PowerShell.ScriptAnalyzer.Instantiation;
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin
8+
{
9+
internal class BuiltinRuleProviderFactory : TypeRuleProviderFactory
10+
{
11+
public BuiltinRuleProviderFactory(
12+
IReadOnlyDictionary<string, IRuleConfiguration> ruleConfigurationCollection)
13+
: base(ruleConfigurationCollection ?? Default.RuleConfiguration, BuiltinRules.DefaultRules)
14+
{
15+
}
16+
}
17+
}

ScriptAnalyzer2/Builtin/Rules/AvoidEmptyCatchBlock.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules
1414
/// </summary>
1515
[IdempotentRule]
1616
[ThreadsafeRule]
17-
[RuleDescription(typeof(Strings), nameof(Strings.AvoidUsingEmptyCatchBlockDescription))]
18-
[Rule("AvoidUsingEmptyCatchBlock")]
17+
[Rule("AvoidUsingEmptyCatchBlock", typeof(Strings), nameof(Strings.AvoidUsingEmptyCatchBlockDescription))]
1918
public class AvoidEmptyCatchBlock : ScriptRule
2019
{
2120
public AvoidEmptyCatchBlock(RuleInfo ruleInfo)

ScriptAnalyzer2/Builtin/Rules/AvoidGlobalVars.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
1616
/// </summary>
1717
[IdempotentRule]
1818
[ThreadsafeRule]
19-
[RuleDescription(typeof(Strings), nameof(Strings.AvoidGlobalVarsDescription))]
20-
[Rule("AvoidGlobalVars")]
19+
[Rule("AvoidGlobalVars", typeof(Strings), nameof(Strings.AvoidGlobalVarsDescription))]
2120
public class AvoidGlobalVars : ScriptRule
2221
{
2322
public AvoidGlobalVars(RuleInfo ruleInfo) : base(ruleInfo)

ScriptAnalyzer2/Builtin/Rules/AvoidUsingWMICmdlet.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules
1414
/// </summary>
1515
[ThreadsafeRule]
1616
[IdempotentRule]
17-
[RuleDescription(typeof(Strings), nameof(Strings.AvoidUsingWMICmdletDescription))]
18-
[Rule("AvoidUsingWMICmdlet")]
17+
[Rule("AvoidUsingWMICmdlet", typeof(Strings), nameof(Strings.AvoidUsingWMICmdletDescription))]
1918
public class AvoidUsingWMICmdlet : ScriptRule
2019
{
2120
public AvoidUsingWMICmdlet(RuleInfo ruleInfo)

ScriptAnalyzer2/Builtin/Rules/UseDeclaredVarsMoreThanAssignments.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
1818
/// </summary>
1919
[IdempotentRule]
2020
[ThreadsafeRule]
21-
[RuleDescription(typeof(Strings), nameof(Strings.UseDeclaredVarsMoreThanAssignmentsDescription))]
22-
[Rule("UseDeclaredVarsMoreThanAssignments")]
21+
[Rule("UseDeclaredVarsMoreThanAssignments", typeof(Strings), nameof(Strings.UseDeclaredVarsMoreThanAssignmentsDescription))]
2322
public class UseDeclaredVarsMoreThanAssignments : ScriptRule
2423
{
2524
public UseDeclaredVarsMoreThanAssignments(RuleInfo ruleInfo) : base(ruleInfo)

ScriptAnalyzer2/Builtin/Rules/UseShouldProcessForStateChangingFunctions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules
1313
/// <summary>
1414
/// UseShouldProcessForStateChangingFunctions: Analyzes the ast to check if ShouldProcess is included in Advanced functions if the Verb of the function could change system state.
1515
/// </summary>
16-
[RuleDescription(typeof(Strings), nameof(Strings.UseShouldProcessForStateChangingFunctionsDescrption))]
17-
[Rule("UseShouldProcessForStateChangingFunctions")]
16+
[Rule("UseShouldProcessForStateChangingFunctions", typeof(Strings), nameof(Strings.UseShouldProcessForStateChangingFunctionsDescrption))]
1817
public class UseShouldProcessForStateChangingFunctions : ScriptRule
1918
{
2019
private static readonly IReadOnlyList<string> s_stateChangingVerbs = new List<string>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using Microsoft.PowerShell.ScriptAnalyzer.Rules;
2+
using System;
3+
using System.Management.Automation;
4+
using System.Management.Automation.Language;
5+
using System.Management.Automation.Runspaces;
6+
using System.Reflection;
7+
using SMA = System.Management.Automation;
8+
9+
namespace Microsoft.PowerShell.ScriptAnalyzer.Commands
10+
{
11+
[Cmdlet(VerbsCommunications.Write, "Diagnostic")]
12+
[OutputType(typeof(ScriptDiagnostic))]
13+
public class WriteDiagnosticCommand : PSCmdlet
14+
{
15+
private RuleInfo _rule;
16+
17+
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)]
18+
public IScriptExtent[] Extent { get; set; }
19+
20+
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)]
21+
public string[] Message { get; set; }
22+
23+
[Parameter]
24+
public DiagnosticSeverity? Severity { get; set; }
25+
26+
protected override void BeginProcessing()
27+
{
28+
_rule = GetRule();
29+
}
30+
31+
protected override void ProcessRecord()
32+
{
33+
for (int i = 0; i < Message.Length; i++)
34+
{
35+
WriteObject(new ScriptDiagnostic(_rule, Message[i], Extent[i], Severity ?? _rule?.DefaultSeverity ?? DiagnosticSeverity.Warning));
36+
}
37+
}
38+
39+
private RuleInfo GetRule()
40+
{
41+
Debugger debugger = Runspace.DefaultRunspace.Debugger;
42+
43+
foreach (CallStackFrame frame in debugger.GetCallStack())
44+
{
45+
if (!(frame.InvocationInfo.MyCommand is FunctionInfo function))
46+
{
47+
continue;
48+
}
49+
50+
if (RuleInfo.TryGetFromFunctionInfo(function, out RuleInfo ruleInfo))
51+
{
52+
return ruleInfo;
53+
}
54+
}
55+
56+
return null;
57+
}
58+
}
59+
}

ScriptAnalyzer2/Instantiation/CompositeRuleProvider.cs

Lines changed: 0 additions & 53 deletions
This file was deleted.

0 commit comments

Comments
 (0)