diff --git a/Engine/Settings.cs b/Engine/Settings.cs index 1cd35f448..539a97fda 100644 --- a/Engine/Settings.cs +++ b/Engine/Settings.cs @@ -416,14 +416,15 @@ private Hashtable GetHashtableFromHashTableAst(HashtableAst hashTableAst) var strConstExprAst = constExprAst as StringConstantExpressionAst; if (strConstExprAst != null) { - rhsList.Add(strConstExprAst.Value); + // it is a string literal + output[key] = strConstExprAst.Value; } else { // it is either an integer or a float output[key] = constExprAst.Value; - continue; } + continue; } else if (pureExp is HashtableAst) { diff --git a/RuleDocumentation/ProvideCommentHelp.md b/RuleDocumentation/ProvideCommentHelp.md index 8be2a1f57..ddfc914bc 100644 --- a/RuleDocumentation/ProvideCommentHelp.md +++ b/RuleDocumentation/ProvideCommentHelp.md @@ -8,9 +8,49 @@ Comment based help should be provided for all PowerShell commands. This test onl For assistance on comment based help, use the command ```Get-Help about_comment_based_help``` or the article, "How to Write Cmdlet Help" (http://go.microsoft.com/fwlink/?LinkID=123415). -## How +## Configuration -Include comment based help for each command identified. +```powershell +Rules = @{ + PSProvideCommentHelp = @{ + Enable = $true + ExportedOnly = $false + BlockComment = $true + VSCodeSnippetCorrection = $false + Placement = "before" + } +} +``` + +### Parameters + +#### Enable: bool (Default valus is `$true`) + +Enable or disable the rule during ScriptAnalyzer invocation. + +#### ExportedOnly: bool (Default value is `$true`) + +If enabled, throw violation only on functions/cmdlets that are exported using the 'Export-ModuleMember' cmdlet. + +#### BlockComment: bool (Default value is `$true`) + +If enabled, returns comment help in block comment style, i.e., `<#...#>`. Otherwise returns +comment help in line comment style, i.e., each comment line starts with `#`. + +#### VSCodeSnippetCorrection: bool (Default value is `$false`) + +If enabled, returns comment help in vscode snippet format. + +#### Placement: string (Default value is `before`) + +Represents the position of comment help with respect to the function definition. + +Possible values are: `before`, `begin` and `end`. If any invalid value is given, the +property defaults to `before`. + +`before` means the help is placed before the function definition. +`begin` means the help is placed at the begining of the function definition body. +`end` means the help is places the end of the function definition body. ## Example diff --git a/Rules/ProvideCommentHelp.cs b/Rules/ProvideCommentHelp.cs index 00411f6f7..66aa87cdb 100644 --- a/Rules/ProvideCommentHelp.cs +++ b/Rules/ProvideCommentHelp.cs @@ -20,6 +20,8 @@ #endif using System.Globalization; using System.Management.Automation; +using System.Text; +using System.IO; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules { @@ -27,64 +29,107 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules /// ProvideCommentHelp: Analyzes ast to check that cmdlets have help. /// #if !CORECLR -[Export(typeof(IScriptRule))] + [Export(typeof(IScriptRule))] #endif - public class ProvideCommentHelp : SkipTypeDefinition, IScriptRule + public class ProvideCommentHelp : ConfigurableRule { - private HashSet exportedFunctions; + private CommentHelpPlacement placement; /// - /// AnalyzeScript: Analyzes the ast to check that cmdlets have help. + /// Construct an object of ProvideCommentHelp type. /// - /// The script's ast - /// The name of the script - /// A List of diagnostic results of this rule - public IEnumerable AnalyzeScript(Ast ast, string fileName) { - if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - - DiagnosticRecords.Clear(); - this.fileName = fileName; - exportedFunctions = Helper.Instance.GetExportedFunction(ast); + public ProvideCommentHelp() : base() + { + // Enable the rule by default + Enable = true; + } - ast.Visit(this); + /// + /// If enabled, throw violation only on functions/cmdlets that are exported using + /// the "Export-ModuleMember" cmdlet. + /// + /// Default value is true. + /// + [ConfigurableRuleProperty(defaultValue: true)] + public bool ExportedOnly { get; protected set; } - return DiagnosticRecords; - } + /// + /// If enabled, returns comment help in block comment style, i.e., `<#...#>`. Otherwise returns + /// comment help in line comment style, i.e., each comment line starts with `#`. + /// + /// Default value is true. + /// + [ConfigurableRuleProperty(defaultValue: true)] + public bool BlockComment { get; protected set; } /// - /// Visit function and checks that it has comment help + /// If enabled, returns comment help in vscode snippet format. + /// + /// Default value is false. /// - /// - /// - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst funcAst) + [ConfigurableRuleProperty(defaultValue: false)] + public bool VSCodeSnippetCorrection { get; protected set; } + + /// + /// Represents the position of comment help with respect to the function definition. + /// + /// Possible values are: `before`, `begin` and `end`. If any invalid value is given, the + /// property defaults to `before`. + /// + /// `before` means the help is placed before the function definition. + /// `begin` means the help is placed at the begining of the function definition body. + /// `end` means the help is places the end of the function definition body. + /// + [ConfigurableRuleProperty(defaultValue: "before")] + public string Placement { - if (funcAst == null) + get { - return AstVisitAction.SkipChildren; + return placement.ToString(); } - - if (exportedFunctions.Contains(funcAst.Name)) + set { - if (funcAst.GetHelpContent() == null) + if (String.IsNullOrWhiteSpace(value) || + !Enum.TryParse(value, true, out placement)) { - DiagnosticRecords.Add( - new DiagnosticRecord( - string.Format(CultureInfo.CurrentCulture, Strings.ProvideCommentHelpError, funcAst.Name), - Helper.Instance.GetScriptExtentForFunctionName(funcAst), - GetName(), - DiagnosticSeverity.Information, - fileName)); + placement = CommentHelpPlacement.Before; } } + } + + private enum CommentHelpPlacement { Before, Begin, End }; - return AstVisitAction.Continue; + /// + /// AnalyzeScript: Analyzes the ast to check that cmdlets have help. + /// + /// The script's ast + /// The name of the script + /// A List of diagnostic results of this rule + public override IEnumerable AnalyzeScript(Ast ast, string fileName) + { + if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + + var exportedFunctions = Helper.Instance.GetExportedFunction(ast); + var violationFinder = new ViolationFinder(exportedFunctions, ExportedOnly); + ast.Visit(violationFinder); + foreach (var functionDefinitionAst in violationFinder.FunctionDefinitionAsts) + { + yield return new DiagnosticRecord( + String.Format(CultureInfo.CurrentCulture, Strings.ProvideCommentHelpError, functionDefinitionAst.Name), + Helper.Instance.GetScriptExtentForFunctionName(functionDefinitionAst), + GetName(), + GetDiagnosticSeverity(), + fileName, + null, + GetCorrection(ast, functionDefinitionAst).ToList()); + } } /// /// GetName: Retrieves the name of this rule. /// /// The name of this rule - public string GetName() + public override string GetName() { return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.ProvideCommentHelpName); } @@ -93,7 +138,7 @@ public string GetName() /// GetCommonName: Retrieves the common name of this rule. /// /// The common name of this rule - public string GetCommonName() + public override string GetCommonName() { return string.Format(CultureInfo.CurrentCulture, Strings.ProvideCommentHelpCommonName); } @@ -102,14 +147,15 @@ public string GetCommonName() /// GetDescription: Retrieves the description of this rule. /// /// The description of this rule - public string GetDescription() { + public override string GetDescription() + { return string.Format(CultureInfo.CurrentCulture, Strings.ProvideCommentHelpDescription); } /// /// Method: Retrieves the type of the rule: builtin, managed or module. /// - public SourceType GetSourceType() + public override SourceType GetSourceType() { return SourceType.Builtin; } @@ -118,7 +164,7 @@ public SourceType GetSourceType() /// GetSeverity: Retrieves the severity of the rule: error, warning of information. /// /// - public RuleSeverity GetSeverity() + public override RuleSeverity GetSeverity() { return RuleSeverity.Information; } @@ -126,13 +172,302 @@ public RuleSeverity GetSeverity() /// /// Method: Retrieves the module/assembly name the rule is from. /// - public string GetSourceName() + public override string GetSourceName() { return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); } - } -} + // TODO replace with extension version + private static IEnumerable GetLines(string text) + { + return text.Split('\n').Select(l => l.Trim('\r')); + } + private DiagnosticSeverity GetDiagnosticSeverity() + { + return DiagnosticSeverity.Information; + } + private IEnumerable GetCorrection(Ast ast, FunctionDefinitionAst funcDefnAst) + { + var helpBuilder = new CommentHelpBuilder(); + // todo replace with an extension version + var paramAsts = (funcDefnAst.Parameters ?? funcDefnAst.Body.ParamBlock?.Parameters) + ?? Enumerable.Empty(); + foreach (var paramAst in paramAsts) + { + helpBuilder.AddParameter(paramAst.Name.VariablePath.UserPath); + } + + int startLine, endLine, startColumn, endColumn; + GetCorrectionPosition(funcDefnAst, out startLine, out endLine, out startColumn, out endColumn); + yield return new CorrectionExtent( + startLine, + endLine, + startColumn, + endColumn, + GetCorrectionText( + helpBuilder.GetCommentHelp(BlockComment, VSCodeSnippetCorrection), + ast, + funcDefnAst), + funcDefnAst.Extent.File); + } + + private string GetCorrectionText(string correction, Ast ast, FunctionDefinitionAst funcDefnAst) + { + var indentationString = String.Empty; + if (funcDefnAst.Extent.StartColumnNumber > 1) + { + indentationString = GetLines(ast.Extent.Text) + .ElementAt(funcDefnAst.Extent.StartLineNumber - 1) + .Substring(0, funcDefnAst.Extent.StartColumnNumber - 1); + correction = String.Join( + Environment.NewLine, + GetLines(correction).Select(l => indentationString + l)); + } + + switch (placement) + { + case CommentHelpPlacement.Begin: + return $"{{{Environment.NewLine}{correction}{Environment.NewLine}"; + + case CommentHelpPlacement.End: + return $"{Environment.NewLine}{correction}{Environment.NewLine}{indentationString}"; + + default: // CommentHelpPlacement.Before + return $"{correction}{Environment.NewLine}"; + } + } + + private void GetCorrectionPosition( + FunctionDefinitionAst funcDefnAst, + out int startLine, + out int endLine, + out int startColumn, + out int endColumn) + { + // the better thing to do is get the line/column from corresponding tokens + switch (placement) + { + case CommentHelpPlacement.Begin: + startLine = funcDefnAst.Body.Extent.StartLineNumber; + endLine = startLine; + startColumn = funcDefnAst.Body.Extent.StartColumnNumber; + endColumn = startColumn + 1; + break; + + case CommentHelpPlacement.End: + startLine = funcDefnAst.Body.Extent.EndLineNumber; + endLine = startLine; + startColumn = funcDefnAst.Body.Extent.EndColumnNumber - 1; + endColumn = startColumn; + break; + + default: // CommentHelpPlacement.Before + startLine = funcDefnAst.Extent.StartLineNumber; + endLine = startLine; + startColumn = 1; + endColumn = startColumn; + break; + } + } + + private class ViolationFinder : AstVisitor + { + private HashSet functionWhitelist; + private List functionDefinitionAsts; + private bool useFunctionWhitelist; + + public ViolationFinder() + { + functionWhitelist = new HashSet(); + functionDefinitionAsts = new List(); + } + + public ViolationFinder(HashSet exportedFunctions) : this() + { + if (exportedFunctions == null) + { + throw new ArgumentNullException(nameof(exportedFunctions)); + } + + this.functionWhitelist = exportedFunctions; + } + + public ViolationFinder(HashSet exportedFunctions, bool exportedOnly) : this(exportedFunctions) + { + this.useFunctionWhitelist = exportedOnly; + } + + public IEnumerable FunctionDefinitionAsts + { + get + { + return functionDefinitionAsts; + } + } + + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst funcAst) + { + if ((!useFunctionWhitelist || functionWhitelist.Contains(funcAst.Name)) + && funcAst.GetHelpContent() == null) + { + functionDefinitionAsts.Add(funcAst); + } + + return AstVisitAction.Continue; + } + } + + private class CommentHelpBuilder + { + private CommentHelpNode synopsis; + private CommentHelpNode description; + private List parameters; + private CommentHelpNode example; + private CommentHelpNode notes; + + public CommentHelpBuilder() + { + synopsis = new CommentHelpNode("Synopsis", "Short description"); + description = new CommentHelpNode("Description", "Long description"); + example = new CommentHelpNode("Example", "An example"); + parameters = new List(); + notes = new CommentHelpNode("Notes", "General notes"); + } + + public void AddParameter(string paramName) + { + parameters.Add(new ParameterHelpNode(paramName, "Parameter description")); + } + + public string GetCommentHelp(bool blockComment, bool snippet) + { + var sb = new StringBuilder(); + var helpContent = snippet ? this.ToSnippetString() : this.ToString(); + if (blockComment) + { + sb.AppendLine("<#"); + sb.AppendLine(helpContent); + sb.Append("#>"); + } + else + { + var boundaryString = new String('#', 30); + sb.AppendLine(boundaryString); + var lines = GetLines(helpContent); + foreach (var line in lines) + { + sb.Append("#"); + sb.AppendLine(line); + } + + sb.Append(boundaryString); + } + + return sb.ToString(); + } + + public override string ToString() + { + return ToString(false); + } + + // todo remove code duplication + public string ToSnippetString() + { + return ToString(true); + } + + private string ToString(bool snippetify) + { + var sb = new StringBuilder(); + var counter = new Counter(snippetify ? (int?)1 : null); + sb.AppendLine(synopsis.ToString(counter.Next())).AppendLine(); + sb.AppendLine(description.ToString(counter.Next())).AppendLine(); + foreach (var parameter in parameters) + { + sb.AppendLine(parameter.ToString(counter.Next())).AppendLine(); + } + + sb.AppendLine(example.ToString(counter.Next())).AppendLine(); + sb.Append(notes.ToString(counter.Next())); + return sb.ToString(); + } + + private class Counter + { + int? count; + + public Counter(int? start) + { + count = start; + } + + public int? Next() + { + return count.HasValue ? count++ : null; + } + } + + private class CommentHelpNode + { + public CommentHelpNode(string nodeName, string description) + { + Name = nodeName; + Description = description; + } + + public string Name { get; } + public string Description { get; set; } + + public override string ToString() + { + return ToString(null); + } + + public virtual string ToString(int? tabStop) + { + var sb = new StringBuilder(); + sb.Append(".").AppendLine(Name.ToUpper()); + if (!String.IsNullOrWhiteSpace(Description)) + { + sb.Append(Snippetify(tabStop, Description)); + } + + return sb.ToString(); + } + + protected string Snippetify(int? tabStop, string defaultValue) + { + return tabStop == null ? defaultValue : $"${{{tabStop}:{defaultValue}}}"; + } + } + + private class ParameterHelpNode : CommentHelpNode + { + public ParameterHelpNode(string parameterName, string parameterDescription) + : base("Parameter", parameterDescription) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } + + public override string ToString() + { + return ToString(null); + } + + public override string ToString(int? tabStop) + { + // todo replace with String.GetLines() extension once it is available + var lines = base.ToString(tabStop).Split('\n').Select(l => l.Trim('\r')).ToArray(); + lines[0] = $"{lines[0]} {ParameterName}"; + return String.Join(Environment.NewLine, lines); + } + } + } + } +} diff --git a/Tests/Engine/Settings.tests.ps1 b/Tests/Engine/Settings.tests.ps1 index 638de0d3a..f4a634ad4 100644 --- a/Tests/Engine/Settings.tests.ps1 +++ b/Tests/Engine/Settings.tests.ps1 @@ -99,7 +99,7 @@ Describe "Settings Class" { } It "Should return 1 rule argument" { - $settings.RuleArguments.Count | Should Be 2 + $settings.RuleArguments.Count | Should Be 3 } It "Should parse boolean type argument" { @@ -109,5 +109,9 @@ Describe "Settings Class" { It "Should parse int type argument" { $settings.RuleArguments["PSUseConsistentIndentation"]["IndentationSize"] | Should Be 4 } + + It "Should parse string literal" { + $settings.RuleArguments["PSProvideCommentHelp"]["Placement"] | Should Be 'end' + } } -} \ No newline at end of file +} diff --git a/Tests/Engine/SettingsTest/Project1/ExplicitSettings.psd1 b/Tests/Engine/SettingsTest/Project1/ExplicitSettings.psd1 index 88d2a6b4f..3e7c8caa3 100644 --- a/Tests/Engine/SettingsTest/Project1/ExplicitSettings.psd1 +++ b/Tests/Engine/SettingsTest/Project1/ExplicitSettings.psd1 @@ -1,8 +1,8 @@ @{ "IncludeRules" = @("PSAvoidUsingCmdletAliases", "PSAvoidUsingWriteHost", "PSUseConsistentIndentation") "ExcludeRules" = @("PSShouldProcess", "PSAvoidUsingWMICmdlet", "PSUseCmdletCorrectly") - "rules" = @{ - PSAvoidUsingCmdletAliases = @{ + "rules" = @{ + PSAvoidUsingCmdletAliases = @{ WhiteList = @("cd", "cp") } @@ -10,5 +10,10 @@ Enable = $true IndentationSize = 4 } + + PSProvideCommentHelp = @{ + Enable = $true + Placement = 'end' + } } -} \ No newline at end of file +} diff --git a/Tests/Rules/ProvideCommentHelp.tests.ps1 b/Tests/Rules/ProvideCommentHelp.tests.ps1 index 693764cbb..834765ec2 100644 --- a/Tests/Rules/ProvideCommentHelp.tests.ps1 +++ b/Tests/Rules/ProvideCommentHelp.tests.ps1 @@ -1,17 +1,51 @@ - +$directory = Split-Path -Parent $MyInvocation.MyCommand.Path +$testRootDirectory = Split-Path -Parent $directory + Import-Module PSScriptAnalyzer +Import-Module (Join-Path $testRootDirectory "PSScriptAnalyzerTestHelper.psm1") + $violationMessage = "The cmdlet 'Comment' does not have a help comment." $violationName = "PSProvideCommentHelp" -$directory = Split-Path -Parent $MyInvocation.MyCommand.Path +$ruleSettings = @{ + Enable = $true + ExportedOnly = $false + BlockComment = $true + Placement = "before" + VSCodeSnippetCorrection = $false +} + +$settings = @{ + IncludeRules = @("PSProvideCommentHelp") + Rules = @{ PSProvideCommentHelp = $ruleSettings } +} + $violations = Invoke-ScriptAnalyzer $directory\BadCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} -if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') -{ +if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { $dscViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} } $noViolations = Invoke-ScriptAnalyzer $directory\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} +function Test-Correction { + param($scriptDef, $expectedCorrection, $settings) + + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDef -Settings $settings + $violations.Count | Should Be 1 + + # We split the lines because appveyor checks out files with "\n" endings + # on windows, which results in inconsistent line endings between corrections + # and result. + $resultLines = $violations[0].SuggestedCorrections[0].Text -split "\r?\n" + $expectedLines = $expectedCorrection -split "\r?\n" + $resultLines.Count | Should Be $expectedLines.Count + for ($i = 0; $i -lt $resultLines.Count; $i++) { + $resultLine = $resultLines[$i] + $expectedLine = $expectedLines[$i] + $resultLine | Should Be $expectedLine + } +} + Describe "ProvideCommentHelp" { Context "When there are violations" { It "has 2 provide comment help violations" { @@ -26,8 +60,279 @@ Describe "ProvideCommentHelp" { $violations[1].Extent.Text | Should Be "Comment" } - if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') - { + It "should find violations in functions that are not exported" { + $def = @' +function foo { +} +'@ + + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Get-Count | Should Be 1 + } + It "should return a help snippet correction with 0 parameters" { + $def = @' +function foo { +} + +Export-ModuleMember -Function foo +'@ + $expectedCorrection = @' +<# +.SYNOPSIS +Short description + +.DESCRIPTION +Long description + +.EXAMPLE +An example + +.NOTES +General notes +#> + +'@ + Test-Correction $def $expectedCorrection $settings + } + + It "should return a help snippet correction with 1 parameters" { + $def = @' +function foo { + param($param1) +} + +Export-ModuleMember -Function foo +'@ + $expectedCorrection = @' +<# +.SYNOPSIS +Short description + +.DESCRIPTION +Long description + +.PARAMETER param1 +Parameter description + +.EXAMPLE +An example + +.NOTES +General notes +#> + +'@ + Test-Correction $def $expectedCorrection $settings + } + + It "should return a help snippet correction with 2 parameters" { + $def = @' +function foo { + param($param1, $param2) +} + +Export-ModuleMember -Function foo +'@ + $expectedCorrection = @' +<# +.SYNOPSIS +Short description + +.DESCRIPTION +Long description + +.PARAMETER param1 +Parameter description + +.PARAMETER param2 +Parameter description + +.EXAMPLE +An example + +.NOTES +General notes +#> + +'@ + Test-Correction $def $expectedCorrection $settings + } + + It "should return a help snippet correction with line comment style" { + $def = @' +function foo { + param($param1, $param2) +} + +Export-ModuleMember -Function foo +'@ + $expectedCorrection = @' +############################## +#.SYNOPSIS +#Short description +# +#.DESCRIPTION +#Long description +# +#.PARAMETER param1 +#Parameter description +# +#.PARAMETER param2 +#Parameter description +# +#.EXAMPLE +#An example +# +#.NOTES +#General notes +############################## + +'@ + $ruleSettings.'BlockComment' = $false + Test-Correction $def $expectedCorrection $settings + } + + It "should return a vscode snippet with line comment style" { + $def = @' +function foo { + param($param1, $param2) +} + +Export-ModuleMember -Function foo +'@ + $expectedCorrection = @' +############################## +#.SYNOPSIS +#${1:Short description} +# +#.DESCRIPTION +#${2:Long description} +# +#.PARAMETER param1 +#${3:Parameter description} +# +#.PARAMETER param2 +#${4:Parameter description} +# +#.EXAMPLE +#${5:An example} +# +#.NOTES +#${6:General notes} +############################## + +'@ + $ruleSettings.'BlockComment' = $false + $ruleSettings.'VSCodeSnippetCorrection' = $true + Test-Correction $def $expectedCorrection $settings + } + + It "should return a help snippet correction with 2 parameters at the begining of function body" { + $def = @' +function foo { + param($param1, $param2) +} +'@ + $expectedCorrection = @' +{ +<# +.SYNOPSIS +Short description + +.DESCRIPTION +Long description + +.PARAMETER param1 +Parameter description + +.PARAMETER param2 +Parameter description + +.EXAMPLE +An example + +.NOTES +General notes +#> + +'@ + $ruleSettings.'ExportedOnly' = $false + $ruleSettings.'BlockComment' = $true + $ruleSettings.'VSCodeSnippetCorrection' = $false + $ruleSettings.'Placement' = 'begin' + Test-Correction $def $expectedCorrection $settings + } + + It "should return a help snippet correction with 2 parameters at the end of function body" { + $def = @' +function foo { + param($param1, $param2) +} +'@ + $expectedCorrection = @' + +<# +.SYNOPSIS +Short description + +.DESCRIPTION +Long description + +.PARAMETER param1 +Parameter description + +.PARAMETER param2 +Parameter description + +.EXAMPLE +An example + +.NOTES +General notes +#> + +'@ + $ruleSettings.'ExportedOnly' = $false + $ruleSettings.'BlockComment' = $true + $ruleSettings.'VSCodeSnippetCorrection' = $false + $ruleSettings.'Placement' = 'end' + Test-Correction $def $expectedCorrection $settings + } + + It "should return a help snippet correction with correct indentation" { + $def = @' + function foo { + param($param1) + } +'@ + $s = ' ' + $expectedCorrection = @" + <# + .SYNOPSIS + Short description +$s$s$s$s + .DESCRIPTION + Long description +$s$s$s$s + .PARAMETER param1 + Parameter description +$s$s$s$s + .EXAMPLE + An example +$s$s$s$s + .NOTES + General notes + #> + +"@ + $ruleSettings.'ExportedOnly' = $false + $ruleSettings.'BlockComment' = $true + $ruleSettings.'VSCodeSnippetCorrection' = $false + $ruleSettings.'Placement' = 'before' + Test-Correction $def $expectedCorrection $settings + } + + + if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { It "Does not count violation in DSC class" { $dscViolations.Count | Should Be 0 } @@ -39,4 +344,4 @@ Describe "ProvideCommentHelp" { $noViolations.Count | Should Be 0 } } -} \ No newline at end of file +}