// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Collections.Generic; #if !CORECLR using System.ComponentModel.Composition; #endif using System.Management.Automation.Language; using System.Globalization; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules { /// /// UseShouldProcessForStateChangingFunctions: Analyzes the ast to check if ShouldProcess is included in Advanced functions if the Verb of the function could change system state. /// #if !CORECLR [Export(typeof(IScriptRule))] #endif public class UseShouldProcessForStateChangingFunctions : IScriptRule { /// /// AnalyzeScript: Analyzes the ast to check if ShouldProcess is included in Advanced functions if the Verb of the function could change system state. /// /// The script's ast /// The script's file name /// A List of diagnostic results of this rule public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (ast == null) { throw new ArgumentNullException(Strings.NullAstErrorMessage); } IEnumerable funcDefWithNoShouldProcessAttrAsts = ast.FindAll(IsStateChangingFunctionWithNoShouldProcessAttribute, true); foreach (FunctionDefinitionAst funcDefAst in funcDefWithNoShouldProcessAttrAsts) { yield return new DiagnosticRecord( string.Format(CultureInfo.CurrentCulture, Strings.UseShouldProcessForStateChangingFunctionsError, funcDefAst.Name), Helper.Instance.GetScriptExtentForFunctionName(funcDefAst), this.GetName(), DiagnosticSeverity.Warning, fileName); } } /// /// Checks if the ast defines a state changing function /// /// /// Returns true or false private bool IsStateChangingFunctionWithNoShouldProcessAttribute(Ast ast) { var funcDefAst = ast as FunctionDefinitionAst; // SupportsShouldProcess is not supported in workflows if (funcDefAst == null || funcDefAst.IsWorkflow) { return false; } return Helper.Instance.IsStateChangingFunctionName(funcDefAst.Name) && (funcDefAst.Body.ParamBlock == null || funcDefAst.Body.ParamBlock.Attributes == null || !HasShouldProcessTrue(funcDefAst.Body.ParamBlock.Attributes)); } /// /// Checks if an attribute has SupportShouldProcess set to $true /// /// /// Returns true or false private bool HasShouldProcessTrue(IEnumerable attributeAsts) { var shouldProcessAttributeAst = Helper.Instance.GetShouldProcessAttributeAst(attributeAsts); if (shouldProcessAttributeAst == null) { return false; } return Helper.Instance.GetNamedArgumentAttributeValue(shouldProcessAttributeAst); } /// /// GetName: Retrieves the name of this rule. /// /// The name of this rule public string GetName() { return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, this.GetSourceName(), Strings.UseShouldProcessForStateChangingFunctionsName); } /// /// GetCommonName: Retrieves the Common name of this rule. /// /// The common name of this rule public string GetCommonName() { return string.Format(CultureInfo.CurrentCulture, Strings.UseShouldProcessForStateChangingFunctionsCommonName); } /// /// GetDescription: Retrieves the description of this rule. /// /// The description of this rule public string GetDescription() { return string.Format(CultureInfo.CurrentCulture, Strings.UseShouldProcessForStateChangingFunctionsDescrption); } /// /// GetSourceType: Retrieves the type of the rule: built-in, managed or module. /// /// Source type {PS, PSDSC} public SourceType GetSourceType() { return SourceType.Builtin; } /// /// GetSeverity: Retrieves the severity of the rule: error, warning of information. /// /// Rule severity {Information, Warning, Error} public RuleSeverity GetSeverity() { return RuleSeverity.Warning; } /// /// GetSourceName: Retrieves the module/assembly name the rule is from. /// /// Source name public string GetSourceName() { return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); } } }