Skip to content

Commit 20f3acf

Browse files
authored
Add beginnings of PSScriptAnalyzer 2.0 (#1476)
* Add beginnings of PSScriptAnalyzer 2.0 * Address @bergmeister's feedback
1 parent 0bdcc32 commit 20f3acf

File tree

64 files changed

+9794
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+9794
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Microsoft.PowerShell.ScriptAnalyzer.Builtin;
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.Builder
8+
{
9+
public class BuiltinRulesBuilder
10+
{
11+
private IReadOnlyDictionary<string, IRuleConfiguration> _ruleConfiguration;
12+
13+
private IRuleComponentProvider _ruleComponents;
14+
15+
public BuiltinRulesBuilder WithRuleConfiguration(IReadOnlyDictionary<string, IRuleConfiguration> ruleConfigurationCollection)
16+
{
17+
_ruleConfiguration = ruleConfigurationCollection;
18+
return this;
19+
}
20+
21+
public BuiltinRulesBuilder WithRuleComponentProvider(IRuleComponentProvider ruleComponentProvider)
22+
{
23+
_ruleComponents = ruleComponentProvider;
24+
return this;
25+
}
26+
27+
public BuiltinRulesBuilder WithRuleComponentBuilder(Action<RuleComponentProviderBuilder> configureRuleComponents)
28+
{
29+
var ruleComponentProviderBuilder = new RuleComponentProviderBuilder();
30+
configureRuleComponents(ruleComponentProviderBuilder);
31+
_ruleComponents = ruleComponentProviderBuilder.Build();
32+
return this;
33+
}
34+
35+
public TypeRuleProvider Build()
36+
{
37+
return TypeRuleProvider.FromTypes(
38+
_ruleConfiguration ?? Default.RuleConfiguration,
39+
_ruleComponents ?? Default.RuleComponentProvider,
40+
BuiltinRules.DefaultRules);
41+
}
42+
}
43+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Microsoft.PowerShell.ScriptAnalyzer.Configuration;
2+
using Microsoft.PowerShell.ScriptAnalyzer.Execution;
3+
using Microsoft.PowerShell.ScriptAnalyzer.Instantiation;
4+
using Microsoft.PowerShell.ScriptAnalyzer.Utils;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Text;
9+
10+
namespace Microsoft.PowerShell.ScriptAnalyzer.Builder
11+
{
12+
public static class ConfiguredBuilding
13+
{
14+
public static ScriptAnalyzer CreateScriptAnalyzer(this IScriptAnalyzerConfiguration configuration)
15+
{
16+
var analyzerBuilder = new ScriptAnalyzerBuilder();
17+
18+
switch (configuration.BuiltinRules ?? BuiltinRulePreference.Default)
19+
{
20+
case BuiltinRulePreference.Aggressive:
21+
case BuiltinRulePreference.Default:
22+
analyzerBuilder.AddBuiltinRules();
23+
break;
24+
}
25+
26+
switch (configuration.RuleExecution ?? RuleExecutionMode.Default)
27+
{
28+
case RuleExecutionMode.Default:
29+
case RuleExecutionMode.Parallel:
30+
analyzerBuilder.WithRuleExecutorFactory(new ParallelLinqRuleExecutorFactory());
31+
break;
32+
33+
case RuleExecutionMode.Sequential:
34+
analyzerBuilder.WithRuleExecutorFactory(new SequentialRuleExecutorFactory());
35+
break;
36+
}
37+
38+
var componentProvider = new RuleComponentProviderBuilder().Build();
39+
40+
if (configuration.RulePaths != null)
41+
{
42+
foreach (string rulePath in configuration.RulePaths)
43+
{
44+
string extension = Path.GetExtension(rulePath);
45+
46+
if (extension.CaseInsensitiveEquals(".dll"))
47+
{
48+
analyzerBuilder.AddRuleProvider(TypeRuleProvider.FromAssemblyFile(configuration.RuleConfiguration, componentProvider, rulePath));
49+
break;
50+
}
51+
}
52+
}
53+
54+
return analyzerBuilder.Build();
55+
}
56+
}
57+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Microsoft.PowerShell.ScriptAnalyzer.Builtin;
2+
using Microsoft.PowerShell.ScriptAnalyzer.Execution;
3+
using Microsoft.PowerShell.ScriptAnalyzer.Instantiation;
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Microsoft.PowerShell.ScriptAnalyzer.Builder
8+
{
9+
public class ScriptAnalyzerBuilder
10+
{
11+
private readonly List<IRuleProvider> _ruleProviders;
12+
13+
private IRuleExecutorFactory _ruleExecutorFactory;
14+
15+
public ScriptAnalyzerBuilder()
16+
{
17+
_ruleProviders = new List<IRuleProvider>();
18+
}
19+
20+
public ScriptAnalyzerBuilder WithRuleExecutorFactory(IRuleExecutorFactory ruleExecutorFactory)
21+
{
22+
_ruleExecutorFactory = ruleExecutorFactory;
23+
return this;
24+
}
25+
26+
public ScriptAnalyzerBuilder AddRuleProvider(IRuleProvider ruleProvider)
27+
{
28+
_ruleProviders.Add(ruleProvider);
29+
return this;
30+
}
31+
32+
public ScriptAnalyzerBuilder AddBuiltinRules()
33+
{
34+
_ruleProviders.Add(TypeRuleProvider.FromTypes(
35+
Default.RuleConfiguration,
36+
Default.RuleComponentProvider,
37+
BuiltinRules.DefaultRules));
38+
return this;
39+
}
40+
41+
public ScriptAnalyzerBuilder AddBuiltinRules(Action<BuiltinRulesBuilder> configureBuiltinRules)
42+
{
43+
var builtinRulesBuilder = new BuiltinRulesBuilder();
44+
configureBuiltinRules(builtinRulesBuilder);
45+
_ruleProviders.Add(builtinRulesBuilder.Build());
46+
return this;
47+
}
48+
49+
public ScriptAnalyzer Build()
50+
{
51+
IRuleProvider ruleProvider = _ruleProviders.Count == 1
52+
? _ruleProviders[0]
53+
: new CompositeRuleProvider(_ruleProviders);
54+
55+
return new ScriptAnalyzer(ruleProvider, _ruleExecutorFactory ?? Default.RuleExecutorFactory);
56+
}
57+
}
58+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Microsoft.PowerShell.ScriptAnalyzer.Builder;
2+
using Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules;
3+
using Microsoft.PowerShell.ScriptAnalyzer.Configuration;
4+
using Microsoft.PowerShell.ScriptAnalyzer.Execution;
5+
using Microsoft.PowerShell.ScriptAnalyzer.Runtime;
6+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Management.Automation.Runspaces;
10+
11+
namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin
12+
{
13+
public static class BuiltinRules
14+
{
15+
public static IReadOnlyList<Type> DefaultRules { get; } = new[]
16+
{
17+
typeof(AvoidEmptyCatchBlock),
18+
typeof(AvoidGlobalVars),
19+
typeof(AvoidPositionalParameters),
20+
typeof(AvoidUsingWMICmdlet),
21+
typeof(UseDeclaredVarsMoreThanAssignments),
22+
typeof(UseShouldProcessForStateChangingFunctions),
23+
};
24+
}
25+
26+
public static class Default
27+
{
28+
private static readonly Lazy<IRuleComponentProvider> s_ruleComponentProviderLazy = new Lazy<IRuleComponentProvider>(BuildRuleComponentProvider);
29+
30+
public static IReadOnlyDictionary<string, IRuleConfiguration> RuleConfiguration { get; } = new Dictionary<string, IRuleConfiguration>(StringComparer.OrdinalIgnoreCase)
31+
{
32+
{ "PS/AvoidUsingEmptyCatchBlock", null },
33+
{ "PS/AvoidGlobalVars", null },
34+
{ "PS/AvoidUsingPositionalParameters", null },
35+
{ "PS/AvoidUsingWMICmdlet", null },
36+
{ "PS/UseDeclaredVarsMoreThanAssignments", null },
37+
{ "PS/UseShouldProcessForStateChangingFunctions", null },
38+
};
39+
40+
public static IRuleExecutorFactory RuleExecutorFactory { get; } = new ParallelLinqRuleExecutorFactory();
41+
42+
public static IRuleComponentProvider RuleComponentProvider => s_ruleComponentProviderLazy.Value;
43+
44+
private static IRuleComponentProvider BuildRuleComponentProvider()
45+
{
46+
return new RuleComponentProviderBuilder()
47+
.AddSingleton(InstantiatePowerShellCommandDatabase())
48+
.Build();
49+
}
50+
51+
private static IPowerShellCommandDatabase InstantiatePowerShellCommandDatabase()
52+
{
53+
using (Runspace runspace = RunspaceFactory.CreateRunspace())
54+
{
55+
runspace.Open();
56+
return SessionStateCommandDatabase.Create(runspace.SessionStateProxy.InvokeCommand);
57+
}
58+
}
59+
}
60+
61+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Management.Automation.Language;
7+
using System.Globalization;
8+
using Microsoft.PowerShell.ScriptAnalyzer.Rules;
9+
10+
namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules
11+
{
12+
/// <summary>
13+
/// AvoidEmptyCatchBlock: Check if any empty catch block is used.
14+
/// </summary>
15+
[IdempotentRule]
16+
[ThreadsafeRule]
17+
[RuleDescription(typeof(Strings), nameof(Strings.AvoidUsingEmptyCatchBlockDescription))]
18+
[Rule("AvoidUsingEmptyCatchBlock")]
19+
public class AvoidEmptyCatchBlock : ScriptRule
20+
{
21+
public AvoidEmptyCatchBlock(RuleInfo ruleInfo)
22+
: base(ruleInfo)
23+
{
24+
}
25+
26+
/// <summary>
27+
/// AnalyzeScript: Analyze the script to check if any empty catch block is used.
28+
/// </summary>
29+
public override IEnumerable<ScriptDiagnostic> AnalyzeScript(Ast ast, IReadOnlyList<Token> tokens, string fileName)
30+
{
31+
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
32+
33+
// Finds all CommandAsts.
34+
IEnumerable<Ast> foundAsts = ast.FindAll(testAst => testAst is CatchClauseAst, true);
35+
36+
// Iterates all CatchClauseAst and check the statements count.
37+
foreach (Ast foundAst in foundAsts)
38+
{
39+
CatchClauseAst catchAst = (CatchClauseAst)foundAst;
40+
41+
if (catchAst.Body.Statements.Count == 0)
42+
{
43+
yield return CreateDiagnostic(
44+
string.Format(CultureInfo.CurrentCulture, Strings.AvoidEmptyCatchBlockError),
45+
catchAst);
46+
}
47+
}
48+
}
49+
}
50+
}
51+
52+
53+
54+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Management.Automation.Language;
7+
using System.Globalization;
8+
using Microsoft.PowerShell.ScriptAnalyzer.Rules;
9+
using Microsoft.PowerShell.ScriptAnalyzer;
10+
using Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules;
11+
12+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
13+
{
14+
/// <summary>
15+
/// AvoidGlobalVars: Analyzes the ast to check that global variables are not used.
16+
/// </summary>
17+
[IdempotentRule]
18+
[ThreadsafeRule]
19+
[RuleDescription(typeof(Strings), nameof(Strings.AvoidGlobalVarsDescription))]
20+
[Rule("AvoidGlobalVars")]
21+
public class AvoidGlobalVars : ScriptRule
22+
{
23+
public AvoidGlobalVars(RuleInfo ruleInfo) : base(ruleInfo)
24+
{
25+
}
26+
27+
/// <summary>
28+
/// AnalyzeScript: Analyzes the ast to check that global variables are not used. From the ILintScriptRule interface.
29+
/// </summary>
30+
/// <param name="ast">The script's ast</param>
31+
/// <param name="fileName">The script's file name</param>
32+
/// <returns>A List of diagnostic results of this rule</returns>
33+
public override IEnumerable<ScriptDiagnostic> AnalyzeScript(Ast ast, IReadOnlyList<Token> tokens, string fileName)
34+
{
35+
IEnumerable<Ast> varAsts = ast.FindAll(testAst => testAst is VariableExpressionAst, true);
36+
37+
if (varAsts == null)
38+
{
39+
yield break;
40+
}
41+
42+
foreach (VariableExpressionAst varAst in varAsts)
43+
{
44+
if (varAst.VariablePath.IsGlobal)
45+
{
46+
yield return CreateDiagnostic(
47+
string.Format(CultureInfo.CurrentCulture, Strings.AvoidGlobalVarsError, varAst.VariablePath.UserPath),
48+
varAst);
49+
}
50+
}
51+
}
52+
}
53+
}
54+
55+
56+
57+

0 commit comments

Comments
 (0)