From cbdd6acd45560275b7dd1b6ab5c3c419bb8b99a1 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 18 Jul 2026 22:55:40 +1200 Subject: [PATCH 1/3] Fix negative blank line indentation --- .../layout/consistent_blank_line_indentation.rb | 2 +- .../layout/consistent_blank_line_indentation.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/rubocop/socketry/layout/consistent_blank_line_indentation.rb b/lib/rubocop/socketry/layout/consistent_blank_line_indentation.rb index 3ac40e8..fa5339f 100644 --- a/lib/rubocop/socketry/layout/consistent_blank_line_indentation.rb +++ b/lib/rubocop/socketry/layout/consistent_blank_line_indentation.rb @@ -75,7 +75,7 @@ def on_new_investigation end end - current_level += delta + current_level = [current_level + delta, 0].max end end diff --git a/test/rubocop/socketry/layout/consistent_blank_line_indentation.rb b/test/rubocop/socketry/layout/consistent_blank_line_indentation.rb index 02c50cf..581dd93 100644 --- a/test/rubocop/socketry/layout/consistent_blank_line_indentation.rb +++ b/test/rubocop/socketry/layout/consistent_blank_line_indentation.rb @@ -830,4 +830,16 @@ def foo expect(offenses.first.message).to be(:include?, message) end end + + with "a top-level blank line after nested array and hash" do + let(:source) {"CONFIG = [[Foo, {\n\tbar: true\n}]\n]\n\nputs CONFIG\n"} + + it "does not crash when clamped deltas would otherwise make indentation negative" do + processed_source = RuboCop::ProcessedSource.new(source, RUBY_VERSION.to_f) + investigator = RuboCop::Cop::Commissioner.new([cop], [], raise_error: true) + report = investigator.investigate(processed_source) + offenses = report.offenses + expect(offenses).to be(:empty?) + end + end end From 06f526b60bc0869348b9988aa96acba5bd48c102 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 18 Jul 2026 23:01:59 +1200 Subject: [PATCH 2/3] Track semantic indentation depth --- .../layout/consistent_blank_line_indentation.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/rubocop/socketry/layout/consistent_blank_line_indentation.rb b/lib/rubocop/socketry/layout/consistent_blank_line_indentation.rb index fa5339f..318de58 100644 --- a/lib/rubocop/socketry/layout/consistent_blank_line_indentation.rb +++ b/lib/rubocop/socketry/layout/consistent_blank_line_indentation.rb @@ -49,6 +49,7 @@ def indentation(width) def on_new_investigation indentation_deltas = build_indentation_deltas current_level = 0 + semantic_level = 0 processed_source.lines.each_with_index do |line, index| line_number = index + 1 @@ -75,7 +76,16 @@ def on_new_investigation end end - current_level = [current_level + delta, 0].max + if delta > 0 + current_level += 1 + semantic_level += delta + elsif delta < 0 + semantic_level += delta + current_level = [current_level, semantic_level].min + end + + current_level = [current_level, 0].max + semantic_level = [semantic_level, 0].max end end @@ -87,10 +97,6 @@ def on_new_investigation def build_indentation_deltas Hash.new(0).tap do |deltas| walk_ast_for_indentation(processed_source.ast, deltas) - - # Cap deltas to a maximum of +1 or -1 per line to handle cases where - # multiple structures open/close on the same line (e.g., `[..., {`) - deltas.transform_values!{|delta| delta.nil? ? nil : delta.clamp(-1, 1)} end end From 61f809f3b0eb116b3dbc119f08c385f51c96c6c6 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 18 Jul 2026 23:06:18 +1200 Subject: [PATCH 3/3] Add release note --- releases.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/releases.md b/releases.md index 87812b5..fa06af7 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Fixed `Layout/ConsistentBlankLineIndentation` to preserve semantic indentation depth for same-line nested structures, avoiding negative indentation levels when later closing lines are processed. + ## v0.10.0 - Fixed `Layout/BlockDelimiterSpacing` to correctly treat a block as a statement (requiring space before `{`) when it is the sole body of a multi-line outer block or method/class/module definition. Previously, the absence of a `:begin` wrapper in the AST caused such blocks (e.g. `Async {foo}` or `let(:bar) {baz}` inside a `describe`/`context` block) to be misclassified as expression-context and have their space incorrectly removed.