Skip to content

RuboCop formatter crashes with IndexError on Ruby 3.4.3 due to off-by-one position calculation #3657

@ianks

Description

@ianks

Ruby LSP Information

Ruby LSP Version: 0.25.0
Ruby Version: 3.4.3
RuboCop Version: 1.78.0
Operating System: macOS Darwin 24.5.0
Editor: Neovim 0.11.2 with AstroNvim
LSP Client: Built-in Neovim LSP

Reproduction steps

  1. Start the Ruby LSP in Neovim with ruby-lsp configured to use RuboCop formatter
  2. Open a Ruby file that ends with a newline character (e.g., a file that is 318 bytes with the last character being a newline)
  3. Trigger formatting (e.g., save the file with format-on-save enabled)
  4. Ruby LSP crashes with an IndexError from RuboCop

Code snippet or error message

File being formatted (/Users/ianks/src/github.com/Shopify/liquid-vm/bench/benchmarks/filters/append.rb):

# frozen_string_literal: true

require_relative "../../bench_helper"
require "yaml"

 environment = { "var0" => "hello", "var1" => "world" }
template =
  "{{ var0 | append: var1 }}{{ var0 | append: 'world' }}{{ 'hello' | append: var1 }}{{ 'hello' | append: 'world' }}"

bench("filter_append", environment:, template:)
00000000  23 20 66 72 6f 7a 65 6e  5f 73 74 72 69 6e 67 5f  |# frozen_string_|
00000010  6c 69 74 65 72 61 6c 3a  20 74 72 75 65 0a 0a 72  |literal: true..r|
00000020  65 71 75 69 72 65 5f 72  65 6c 61 74 69 76 65 20  |equire_relative |
00000030  22 2e 2e 2f 2e 2e 2f 62  65 6e 63 68 5f 68 65 6c  |"../../bench_hel|
00000040  70 65 72 22 0a 72 65 71  75 69 72 65 20 22 79 61  |per".require "ya|
00000050  6d 6c 22 0a 0a 20 65 6e  76 69 72 6f 6e 6d 65 6e  |ml".. environmen|
00000060  74 20 3d 20 7b 20 22 76  61 72 30 22 20 3d 3e 20  |t = { "var0" => |
00000070  22 68 65 6c 6c 6f 22 2c  20 22 76 61 72 31 22 20  |"hello", "var1" |
00000080  3d 3e 20 22 77 6f 72 6c  64 22 20 7d 0a 74 65 6d  |=> "world" }.tem|
00000090  70 6c 61 74 65 20 3d 0a  20 20 22 7b 7b 20 76 61  |plate =.  "{{ va|
000000a0  72 30 20 7c 20 61 70 70  65 6e 64 3a 20 76 61 72  |r0 | append: var|
000000b0  31 20 7d 7d 7b 7b 20 76  61 72 30 20 7c 20 61 70  |1 }}{{ var0 | ap|
000000c0  70 65 6e 64 3a 20 27 77  6f 72 6c 64 27 20 7d 7d  |pend: 'world' }}|
000000d0  7b 7b 20 27 68 65 6c 6c  6f 27 20 7c 20 61 70 70  |{{ 'hello' | app|
000000e0  65 6e 64 3a 20 76 61 72  31 20 7d 7d 7b 7b 20 27  |end: var1 }}{{ '|
000000f0  68 65 6c 6c 6f 27 20 7c  20 61 70 70 65 6e 64 3a  |hello' | append:|
00000100  20 27 77 6f 72 6c 64 27  20 7d 7d 22 0a 0a 62 65  | 'world' }}"..be|
00000110  6e 63 68 28 22 66 69 6c  74 65 72 5f 61 70 70 65  |nch("filter_appe|
00000120  6e 64 22 2c 20 65 6e 76  69 72 6f 6e 6d 65 6e 74  |nd", environment|
00000130  3a 2c 20 74 65 6d 70 6c  61 74 65 3a 29 0a        |:, template:).|
0000013e

Error message:

Formatting failed with
: /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/lsp/stdin_runner.rb:67:in 'RuboCop::Lsp::StdinRunner#run': An internal error occurred for the Layout/EmptyLinesAroundArguments cop. (RubyLsp::Requests::Support::InternalRuboCopError)
Updating to a newer version of RuboCop may solve this.
For more details, run RuboCop on the command line.

        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/lsp/runtime.rb:43:in 'RuboCop::LSP::Runtime#format'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/ruby_lsp/rubocop/runtime_adapter.rb:28:in 'RubyLsp::RuboCop::RuntimeAdapter#run_formatting'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/requests/formatting.rb:35:in 'RubyLsp::Requests::Formatting#perform'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/server.rb:657:in 'RubyLsp::Server#text_document_formatting'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/server.rb:46:in 'RubyLsp::Server#process_message'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/base_server.rb:162:in 'block in RubyLsp::BaseServer#new_worker'
/Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:176:in 'RuboCop::Cop::Commissioner#with_cop_error_handling': cause: #<IndexError: The range 317...318 is outside the bounds of the source> (RuboCop::ErrorWithAnalyzedFileLocation)
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:106:in 'block in RuboCop::Cop::Commissioner#trigger_responding_cops'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:105:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:105:in 'RuboCop::Cop::Commissioner#trigger_responding_cops'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:69:in 'RuboCop::Cop::Commissioner#on_send'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-ast-1.45.1/lib/rubocop/ast/traversal.rb:146:in 'block in RuboCop::AST::Traversal#on_dstr'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-ast-1.45.1/lib/rubocop/ast/traversal.rb:146:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-ast-1.45.1/lib/rubocop/ast/traversal.rb:146:in 'RuboCop::AST::Traversal#on_dstr'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:71:in 'RuboCop::Cop::Commissioner#on_begin'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-ast-1.45.1/lib/rubocop/ast/traversal.rb:20:in 'RuboCop::AST::Traversal#walk'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:87:in 'RuboCop::Cop::Commissioner#investigate'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/team.rb:174:in 'RuboCop::Cop::Team#investigate_partial'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/team.rb:101:in 'RuboCop::Cop::Team#investigate'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:348:in 'block in RuboCop::Runner#inspect_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:347:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:347:in 'Enumerable#flat_map'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:347:in 'RuboCop::Runner#inspect_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:290:in 'block in RuboCop::Runner#do_inspection_loop'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:324:in 'block in RuboCop::Runner#iterate_until_no_changes'
        from <internal:kernel>:168:in 'Kernel#loop'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:317:in 'RuboCop::Runner#iterate_until_no_changes'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:286:in 'RuboCop::Runner#do_inspection_loop'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:167:in 'block in RuboCop::Runner#file_offenses'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:192:in 'RuboCop::Runner#file_offense_cache'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:166:in 'RuboCop::Runner#file_offenses'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:154:in 'RuboCop::Runner#process_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:135:in 'block in RuboCop::Runner#each_inspected_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:134:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:134:in 'Enumerable#reduce'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:134:in 'RuboCop::Runner#each_inspected_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:120:in 'RuboCop::Runner#inspect_files'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:73:in 'RuboCop::Runner#run'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/lsp/stdin_runner.rb:54:in 'RuboCop::Lsp::StdinRunner#run'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/lsp/runtime.rb:43:in 'RuboCop::LSP::Runtime#format'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/ruby_lsp/rubocop/runtime_adapter.rb:28:in 'RubyLsp::RuboCop::RuntimeAdapter#run_formatting'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/requests/formatting.rb:35:in 'RubyLsp::Requests::Formatting#perform'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/server.rb:657:in 'RubyLsp::Server#text_document_formatting'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/server.rb:46:in 'RubyLsp::Server#process_message'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/base_server.rb:162:in 'block in RubyLsp::BaseServer#new_worker'
/Users/ianks/.gem/ruby/3.4.3/gems/parser-3.3.8.0/lib/parser/source/tree_rewriter.rb:406:in 'Parser::Source::TreeRewriter#check_range_validity': The range 317...318 is outside the bounds of the source (IndexError)
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/corrector.rb:120:in 'RuboCop::Cop::Corrector#check_range_validity'
        from /Users/ianks/.gem/ruby/3.4.3/gems/parser-3.3.8.0/lib/parser/source/tree_rewriter.rb:398:in 'Parser::Source::TreeRewriter#combine'
        from /Users/ianks/.gem/ruby/3.4.3/gems/parser-3.3.8.0/lib/parser/source/tree_rewriter.rb:194:in 'Parser::Source::TreeRewriter#replace'
        from /Users/ianks/.gem/ruby/3.4.3/gems/parser-3.3.8.0/lib/parser/source/tree_rewriter.rb:218:in 'Parser::Source::TreeRewriter#remove'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/layout/empty_lines_around_arguments.rb:53:in 'block (2 levels) in RuboCop::Cop::Layout::EmptyLinesAroundArguments#on_send'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/base.rb:426:in 'RuboCop::Cop::Base#correct'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/base.rb:210:in 'RuboCop::Cop::Base#add_offense'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/layout/empty_lines_around_arguments.rb:52:in 'block in RuboCop::Cop::Layout::EmptyLinesAroundArguments#on_send'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/layout/empty_lines_around_arguments.rb:74:in 'block in RuboCop::Cop::Layout::EmptyLinesAroundArguments#extra_lines'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/layout/empty_lines_around_arguments.rb:72:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/layout/empty_lines_around_arguments.rb:72:in 'RuboCop::Cop::Layout::EmptyLinesAroundArguments#extra_lines'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/layout/empty_lines_around_arguments.rb:51:in 'RuboCop::Cop::Layout::EmptyLinesAroundArguments#on_send'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:107:in 'Kernel#public_send'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:107:in 'block (2 levels) in RuboCop::Cop::Commissioner#trigger_responding_cops'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:171:in 'RuboCop::Cop::Commissioner#with_cop_error_handling'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:106:in 'block in RuboCop::Cop::Commissioner#trigger_responding_cops'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:105:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:105:in 'RuboCop::Cop::Commissioner#trigger_responding_cops'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:69:in 'RuboCop::Cop::Commissioner#on_send'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-ast-1.45.1/lib/rubocop/ast/traversal.rb:146:in 'block in RuboCop::AST::Traversal#on_dstr'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-ast-1.45.1/lib/rubocop/ast/traversal.rb:146:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-ast-1.45.1/lib/rubocop/ast/traversal.rb:146:in 'RuboCop::AST::Traversal#on_dstr'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:71:in 'RuboCop::Cop::Commissioner#on_begin'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-ast-1.45.1/lib/rubocop/ast/traversal.rb:20:in 'RuboCop::AST::Traversal#walk'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/commissioner.rb:87:in 'RuboCop::Cop::Commissioner#investigate'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/team.rb:174:in 'RuboCop::Cop::Team#investigate_partial'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/cop/team.rb:101:in 'RuboCop::Cop::Team#investigate'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:348:in 'block in RuboCop::Runner#inspect_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:347:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:347:in 'Enumerable#flat_map'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:347:in 'RuboCop::Runner#inspect_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:290:in 'block in RuboCop::Runner#do_inspection_loop'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:324:in 'block in RuboCop::Runner#iterate_until_no_changes'
        from <internal:kernel>:168:in 'Kernel#loop'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:317:in 'RuboCop::Runner#iterate_until_no_changes'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:286:in 'RuboCop::Runner#do_inspection_loop'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:167:in 'block in RuboCop::Runner#file_offenses'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:192:in 'RuboCop::Runner#file_offense_cache'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:166:in 'RuboCop::Runner#file_offenses'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:154:in 'RuboCop::Runner#process_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:135:in 'block in RuboCop::Runner#each_inspected_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:134:in 'Array#each'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:134:in 'Enumerable#reduce'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:134:in 'RuboCop::Runner#each_inspected_file'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:120:in 'RuboCop::Runner#inspect_files'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/runner.rb:73:in 'RuboCop::Runner#run'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/lsp/stdin_runner.rb:54:in 'RuboCop::Lsp::StdinRunner#run'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/rubocop/lsp/runtime.rb:43:in 'RuboCop::LSP::Runtime#format'
        from /Users/ianks/.gem/ruby/3.4.3/gems/rubocop-1.78.0/lib/ruby_lsp/rubocop/runtime_adapter.rb:28:in 'RubyLsp::RuboCop::RuntimeAdapter#run_formatting'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/requests/formatting.rb:35:in 'RubyLsp::Requests::Formatting#perform'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/server.rb:657:in 'RubyLsp::Server#text_document_formatting'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/server.rb:46:in 'RubyLsp::Server#process_message'
        from /Users/ianks/.gem/ruby/3.4.3/gems/ruby-lsp-0.25.0/lib/ruby_lsp/base_server.rb:162:in 'block in RubyLsp::BaseServer#new_worker'

Additional Context

  • The file is exactly 318 bytes with US-ASCII encoding
  • The error occurs when RuboCop's Layout/EmptyLinesAroundArguments cop tries to access range 317...318
  • Running rubocop --lsp directly on the file works without issues
  • The issue appears to be related to how ruby-lsp calculates or passes text positions to RuboCop
  • This might be related to UTF-8 vs UTF-16 position encoding differences or how line endings are counted

Workaround

Setting formatter: "none" or using an alternative formatter like syntax_tree avoids the crash, but this disables RuboCop formatting which many projects rely on.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions