diff --git a/Rules/UseConsistentIndentation.cs b/Rules/UseConsistentIndentation.cs index 2c77787c6..97c913db5 100644 --- a/Rules/UseConsistentIndentation.cs +++ b/Rules/UseConsistentIndentation.cs @@ -298,7 +298,10 @@ public override IEnumerable AnalyzeScript(Ast ast, string file } bool lineHasPipelineBeforeToken = LineHasPipelineBeforeToken(tokens, tokenIndex, token); - AddViolation(token, tempIndentationLevel, diagnosticRecords, ref onNewLine, lineHasPipelineBeforeToken); + int commentIndentationOffset = token.Kind == TokenKind.Comment + ? GetCommentRunIndentationOffset(tokens, tokenIndex) + : 0; + AddViolation(token, tempIndentationLevel, diagnosticRecords, ref onNewLine, lineHasPipelineBeforeToken, commentIndentationOffset); } break; } @@ -336,6 +339,31 @@ public override IEnumerable AnalyzeScript(Ast ast, string file return diagnosticRecords; } + private static int GetCommentRunIndentationOffset(Token[] tokens, int tokenIndex) + { + int firstCommentIndex = tokenIndex; + int currentLine = tokens[tokenIndex].Extent.StartLineNumber; + int precedingIndex = tokenIndex - 1; + while (precedingIndex >= 0 && tokens[precedingIndex].Kind == TokenKind.NewLine) + { + precedingIndex--; + if (precedingIndex < 0 || + tokens[precedingIndex].Kind != TokenKind.Comment || + tokens[precedingIndex].Extent.EndLineNumber != currentLine - 1 || + (precedingIndex > 0 && tokens[precedingIndex - 1].Kind != TokenKind.NewLine)) + { + break; + } + + firstCommentIndex = precedingIndex; + currentLine = tokens[precedingIndex].Extent.StartLineNumber; + precedingIndex--; + } + + return tokens[tokenIndex].Extent.StartColumnNumber - + tokens[firstCommentIndex].Extent.StartColumnNumber; + } + /// /// Scans forward from the current opener to the end of the line. /// Returns true if there is at least one unclosed opener when @@ -612,12 +640,14 @@ private void AddViolation( int expectedIndentationLevel, List diagnosticRecords, ref bool onNewLine, - bool lineHasPipelineBeforeToken = false) + bool lineHasPipelineBeforeToken = false, + int indentationOffset = 0) { if (onNewLine) { onNewLine = false; - if (token.Extent.StartColumnNumber - 1 != GetIndentation(expectedIndentationLevel)) + int expectedIndentation = Math.Max(0, GetIndentation(expectedIndentationLevel) + indentationOffset); + if (token.Extent.StartColumnNumber - 1 != expectedIndentation) { var fileName = token.Extent.File; var extent = token.Extent; @@ -640,14 +670,14 @@ private void AddViolation( GetDiagnosticSeverity(), fileName, null, - GetSuggestedCorrections(token, expectedIndentationLevel, lineHasPipelineBeforeToken))); + GetSuggestedCorrections(token, expectedIndentation, lineHasPipelineBeforeToken))); } } } private List GetSuggestedCorrections( Token token, - int indentationLevel, + int indentation, bool lineHasPipelineBeforeToken = false) { // TODO Add another constructor for correction extent that takes extent @@ -661,7 +691,7 @@ private List GetSuggestedCorrections( token.Extent.EndLineNumber, 1, token.Extent.EndColumnNumber, - GetIndentationString(indentationLevel) + optionalPipeline + token.Extent.Text, + new string(indentationChar, indentation) + optionalPipeline + token.Extent.Text, token.Extent.File)); return corrections; } @@ -677,9 +707,5 @@ private int GetIndentation(int indentationLevel) return indentationLevel * indentationLevelMultiplier; } - private string GetIndentationString(int indentationLevel) - { - return new string(indentationChar, GetIndentation(indentationLevel)); - } } } diff --git a/Tests/Rules/UseConsistentIndentation.tests.ps1 b/Tests/Rules/UseConsistentIndentation.tests.ps1 index 6bf241116..3a0f325e2 100644 --- a/Tests/Rules/UseConsistentIndentation.tests.ps1 +++ b/Tests/Rules/UseConsistentIndentation.tests.ps1 @@ -204,8 +204,58 @@ function foo { } } - Context "When a multi-line command is given" { + Context "When a commented-out code block is provided" { + + It 'Should preserve relative indentation in commented-out code with indentation' -TestCases @( + @{ Kind = 'space'; Indentation = ' ' } + @{ Kind = 'tab'; Indentation = "`t" } + ) { + param([string] $Kind, [string] $Indentation) + $scriptDefinition = @( + 'if ($a) {' + "$Indentation# if (`$b) {" + "$Indentation$Indentation# doThing" + "$Indentation# }" + "$($Indentation)Write-Output 'after'" + '}' + ) -join "`n" + $settings.Rules.PSUseConsistentIndentation.Kind = $Kind + $settings.Rules.PSUseConsistentIndentation.PipelineIndentation = 'NoIndentation' + Invoke-Formatter -ScriptDefinition $scriptDefinition -Settings $settings | + Should -Be $scriptDefinition + } + + It 'Should shift a commented-out code block without flattening it with indentation' -TestCases @( + @{ Kind = 'space'; Indentation = ' ' } + @{ Kind = 'tab'; Indentation = "`t" } + ) { + param([string] $Kind, [string] $Indentation) + $scriptDefinition = @( + 'if ($a) {' + '# if ($b) {' + "$Indentation# doThing" + '# }' + "$($Indentation)Write-Output 'after'" + '}' + ) -join "`n" + $expectedScriptDefinition = @( + 'if ($a) {' + "$Indentation# if (`$b) {" + "$Indentation$Indentation# doThing" + "$Indentation# }" + "$($Indentation)Write-Output 'after'" + '}' + ) -join "`n" + $settings.Rules.PSUseConsistentIndentation.Kind = $Kind + $settings.Rules.PSUseConsistentIndentation.PipelineIndentation = 'NoIndentation' + + Invoke-Formatter -ScriptDefinition $scriptDefinition -Settings $settings | + Should -Be $expectedScriptDefinition + } + } + + Context "When a multi-line command is given" { It "When a comment is in the middle of a multi-line statement with preceding and succeeding line continuations" { $scriptDefinition = @' foo `