From 1503ab562c104e359f10699139301bddd60f8b9e Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 26 Feb 2026 09:03:44 +0000 Subject: [PATCH] Extend marker DSL Replace positional `## directive: file:line:col` references with named markers (`## directive: [A]`) throughout scenario files. This makes scenarios more readable and resilient to line-number changes. --- scenario/class/initialize.rb | 6 +- scenario/definitions/definition-rbs.rb | 6 +- scenario/definitions/definition.rb | 7 +- .../definition_multibyte_characters.rb | 7 +- scenario/rbs/attr.rb | 12 ++- scenario/rbs/inline-hover.rb | 12 ++- scenario/service/completion_dot.rb | 6 +- scenario/service/completion_dot_module.rb | 4 +- scenario/service/definition.rb | 31 +++++--- scenario/service/hover.rb | 9 ++- scenario/service/hover2.rb | 12 ++- scenario/service/hover3.rb | 3 +- scenario/service/hover4.rb | 24 ++++-- scenario/service/references.rb | 6 +- scenario/service/references2.rb | 6 +- scenario/service/rename.rb | 3 +- scenario/service/rename2.rb | 3 +- scenario/service/rename3.rb | 7 +- scenario/service/rename4.rb | 8 +- test/scenario_compiler.rb | 75 +++++++++++++++++-- 20 files changed, 183 insertions(+), 64 deletions(-) diff --git a/scenario/class/initialize.rb b/scenario/class/initialize.rb index 284208e34..0ff4821e6 100644 --- a/scenario/class/initialize.rb +++ b/scenario/class/initialize.rb @@ -4,6 +4,7 @@ class A class B def initialize(xxx) # 5 +# ^[A] @xxx = xxx end end @@ -13,6 +14,7 @@ class C def foo B.new(1) # 14 +# ^[B] end ## assert @@ -27,8 +29,8 @@ class Object def foo: -> B end -## hover: test.rb:5:19 +## hover: [A] Integer -## hover: test.rb:14:6 +## hover: [B] B#initialize : (Integer) -> void diff --git a/scenario/definitions/definition-rbs.rb b/scenario/definitions/definition-rbs.rb index cdbaccfbb..a055899b1 100644 --- a/scenario/definitions/definition-rbs.rb +++ b/scenario/definitions/definition-rbs.rb @@ -16,11 +16,13 @@ def call_1 end Animal::Cat.new.call_1 +# ^[A] Animal::Cat.new.call_2 +# ^[B] -## definition: test.rb:9:17 +## definition: [A] test.rbs:(3,8)-(3,14) test.rb:(3,8)-(3,14) -## definition: test.rb:10:17 +## definition: [B] test.rbs:(4,8)-(4,14) diff --git a/scenario/definitions/definition.rb b/scenario/definitions/definition.rb index f13deaceb..3dd4012d8 100644 --- a/scenario/definitions/definition.rb +++ b/scenario/definitions/definition.rb @@ -8,12 +8,13 @@ def call end Animal::Cat.new.call +#^[A] ^[B] ^[C] -## definition: test.rb:9:1 +## definition: [A] test.rb:(1,7)-(1,13) -## definition: test.rb:9:9 +## definition: [B] test.rb:(2,8)-(2,11) -## definition: test.rb:9:17 +## definition: [C] test.rb:(3,8)-(3,12) diff --git a/scenario/definitions/definition_multibyte_characters.rb b/scenario/definitions/definition_multibyte_characters.rb index 9dc3adb45..4e770ba4a 100644 --- a/scenario/definitions/definition_multibyte_characters.rb +++ b/scenario/definitions/definition_multibyte_characters.rb @@ -8,12 +8,13 @@ def 🐱🐱🐱 end A動物::B猫.new.🐱🐱🐱 +#^[A] ^[B] ^[C] -## definition: test.rb:9:1 +## definition: [A] test.rb:(1,7)-(1,10) -## definition: test.rb:9:6 +## definition: [B] test.rb:(2,8)-(2,10) -## definition: test.rb:9:13 +## definition: [C] test.rb:(3,8)-(3,14) diff --git a/scenario/rbs/attr.rb b/scenario/rbs/attr.rb index 764cf677f..c06901c06 100644 --- a/scenario/rbs/attr.rb +++ b/scenario/rbs/attr.rb @@ -10,13 +10,17 @@ class A def test1 a = A.new a.foo = 42.0 +# ^[A] a.foo +# ^[B] end def test2 a = A.new a.bar = "foo" +# ^[C] a.bar +# ^[D] end ## assert: test.rb @@ -25,14 +29,14 @@ def test1: -> Integer def test2: -> String end -## hover: test.rb:3:4 +## hover: [A] A#foo= : (Integer | Float) -> Integer | Float -## hover: test.rb:4:4 +## hover: [B] A#foo : -> Integer -## hover: test.rb:9:4 +## hover: [C] A#bar= : (String) -> String -## hover: test.rb:10:4 +## hover: [D] A#bar : -> String diff --git a/scenario/rbs/inline-hover.rb b/scenario/rbs/inline-hover.rb index 41ba7b6c0..d5c21f6dd 100644 --- a/scenario/rbs/inline-hover.rb +++ b/scenario/rbs/inline-hover.rb @@ -1,25 +1,29 @@ ## update: test.rb #: (Integer) -> void def check(var) +# ^[A] var +# ^[B] end -## hover: test.rb:2:11 +## hover: [A] Integer -## hover: test.rb:3:3 +## hover: [B] Integer ## update: test2.rb class Foo #: (String) -> void def bar(x) +# ^[C] x +# ^[D] end end -## hover: test2.rb:3:10 +## hover: [C] String -## hover: test2.rb:4:4 +## hover: [D] String diff --git a/scenario/service/completion_dot.rb b/scenario/service/completion_dot.rb index 4ab2ba245..ad5f22e58 100644 --- a/scenario/service/completion_dot.rb +++ b/scenario/service/completion_dot.rb @@ -14,21 +14,23 @@ def baz(_) def test1(x) x +# ^[A] end def test2 test1(Foo.new) +# ^[B] end Foo.new.foo(1.0) test(Foo.new) -## completion: test.rb:15:2 +## completion: [A] Foo#foo : (Float) -> Integer Foo#bar : (untyped) -> String Foo#baz : (Foo) -> Foo -## completion: test.rb:19:15 +## completion: [B] Foo#foo : (Float) -> Integer Foo#bar : (untyped) -> String Foo#baz : (Foo) -> Foo diff --git a/scenario/service/completion_dot_module.rb b/scenario/service/completion_dot_module.rb index 1633e6b29..4ac55c20f 100644 --- a/scenario/service/completion_dot_module.rb +++ b/scenario/service/completion_dot_module.rb @@ -11,8 +11,10 @@ def foo = :FOO end x = C.new x +#\ +^[A] -## completion: test.rb:12:0 +## completion: [A] C#foo : -> :FOO M#bar : -> :BAR P#baz : -> :BAZ diff --git a/scenario/service/definition.rb b/scenario/service/definition.rb index 2f6162222..f45f84c53 100644 --- a/scenario/service/definition.rb +++ b/scenario/service/definition.rb @@ -14,46 +14,55 @@ def foo(n) end Foo.new(1).foo(1.0) +#^[A]^[B] ^[C] Foo.new(1).bar_sym_reader +# ^[D] Foo.new(1).baz_sym_reader +# ^[E] Foo.new(1).bar_sym_accessor +# ^[F] Foo.new(1).baz_sym_accessor +# ^[G] Foo::BAR +# ^[H] Foo::BBAR = 1 Foo::BBAR +# ^[I] Foo::CCAR, Foo::DDAR = [1, 2] Foo::CCAR +# ^[J] Foo::DDAR +# ^[K] -## definition: test.rb:15:1 +## definition: [A] test.rb:(1,6)-(1,9) # Jump to Foo class -## definition: test.rb:15:5 +## definition: [B] test.rb:(7,6)-(7,16) # Jump Foo.initialize from Foo.new -## definition: test.rb:15:12 +## definition: [C] test.rb:(11,6)-(11,9) # Jump to Foo#foo -## definition: test.rb:16:12 +## definition: [D] test.rb:(4,14)-(4,29) # Jump to Foo#bar_sym_reader (first sym arg of attr_reader) -## definition: test.rb:17:12 +## definition: [E] test.rb:(4,31)-(4,46) # Jump to Foo#baz_sym_reader (second sym arg of attr_reader) -## definition: test.rb:18:12 +## definition: [F] test.rb:(5,16)-(5,33) # Jump to Foo#bar_sym_accessor (first sym arg of attr_accessor) -## definition: test.rb:19:12 +## definition: [G] test.rb:(5,35)-(5,52) # Jump to Foo#baz_sym_accessor (second sym arg of attr_accessor) -## definition: test.rb:20:5 +## definition: [H] test.rb:(2,2)-(2,5) # Jump to Foo#BAR (constant_write_node) -## definition: test.rb:22:5 +## definition: [I] test.rb:(21,0)-(21,9) # Jump to Foo#BBAR (constant_path_write_node) -## definition: test.rb:24:5 +## definition: [J] test.rb:(23,0)-(23,9) # Jump to Foo#CCAR (first arg of constant_path_target_node) -## definition: test.rb:25:5 +## definition: [K] test.rb:(23,11)-(23,20) # Jump to Foo#DDAR (second arg of constant_path_target_node) diff --git a/scenario/service/hover.rb b/scenario/service/hover.rb index 6d3fc8843..2023702d1 100644 --- a/scenario/service/hover.rb +++ b/scenario/service/hover.rb @@ -1,26 +1,29 @@ ## update def foo(variable) +# ^[A] variable + 1 +# ^[B] end def main(_) foo(2) end -## hover: test.rb:1:10 +## hover: [A] Integer -## hover: test.rb:2:3 +## hover: [B] Integer ## update def foo(nnn) nnn.times do |var| var +# ^[C] end end foo(42) -## hover: test.rb:3:4 +## hover: [C] Integer diff --git a/scenario/service/hover2.rb b/scenario/service/hover2.rb index e59db924a..4ece18a90 100644 --- a/scenario/service/hover2.rb +++ b/scenario/service/hover2.rb @@ -5,8 +5,10 @@ def foo end foo +#\ +^[A] -## hover: test.rb:6:0 +## hover: [A] Object#foo : -> Integer ## diagnostics @@ -19,8 +21,10 @@ def foo end foo +#\ +^[B] -## hover: test.rb:6:0 +## hover: [B] Object#foo : -> Integer? ## update: test.rb @@ -30,8 +34,10 @@ def foo end foo +#\ +^[C] # TODO: The above test is mainly for SIG_TYPE#show -## hover: test.rb:6:0 +## hover: [C] Object#foo : -> (Integer | String)? diff --git a/scenario/service/hover3.rb b/scenario/service/hover3.rb index b87bc0099..4005514cb 100644 --- a/scenario/service/hover3.rb +++ b/scenario/service/hover3.rb @@ -1,9 +1,10 @@ ## update: test.rb x = { 1 => 2 } x.map do |k, v| +# ^[A] end # TODO: support showing type parameters -## hover: test.rb:2:3 +## hover: [A] Hash[Integer, Integer]#map : -> Array[...] | -> Enumerator[...] diff --git a/scenario/service/hover4.rb b/scenario/service/hover4.rb index e2b680c56..691e3f009 100644 --- a/scenario/service/hover4.rb +++ b/scenario/service/hover4.rb @@ -19,34 +19,42 @@ def rest_keywords: (**untyped) -> Integer ## update: test.rb required_positional_args +#^[A] optional_positional_args +#^[B] post_required_positional_args +#^[C] rest_positional_args +#^[D] rest_post_positional_args +#^[E] required_keywords +#^[F] optional_keywords +#^[G] rest_keywords +#^[H] -## hover: test.rb:1:1 +## hover: [A] Object#required_positional_args : (Integer) -> Integer -## hover: test.rb:2:1 +## hover: [B] Object#optional_positional_args : (?Integer) -> Integer -## hover: test.rb:3:1 +## hover: [C] Object#post_required_positional_args : (?Integer, Integer) -> Integer -## hover: test.rb:4:1 +## hover: [D] Object#rest_positional_args : (*Integer) -> Integer -## hover: test.rb:5:1 +## hover: [E] Object#rest_post_positional_args : (*Integer, Integer) -> Integer -## hover: test.rb:6:1 +## hover: [F] Object#required_keywords : (a: Integer) -> Integer -## hover: test.rb:7:1 +## hover: [G] Object#optional_keywords : (?a: Integer) -> Integer -## hover: test.rb:8:1 +## hover: [H] Object#rest_keywords : (**untyped) -> Integer diff --git a/scenario/service/references.rb b/scenario/service/references.rb index 891d3b461..2ad3642cc 100644 --- a/scenario/service/references.rb +++ b/scenario/service/references.rb @@ -1,9 +1,11 @@ ## update: test.rb class Foo def initialize(n) +# ^[A] end def foo(n) +# ^[B] end end @@ -11,12 +13,12 @@ def foo(n) Foo.new(1).foo(1.0) Foo.new(1).foo(1.0) -## references: test.rb:2:7 +## references: [A] test.rb:(9,0)-(9,10) test.rb:(10,0)-(10,10) test.rb:(11,0)-(11,10) -## references: test.rb:5:7 +## references: [B] test.rb:(9,0)-(9,19) test.rb:(10,0)-(10,19) test.rb:(11,0)-(11,19) diff --git a/scenario/service/references2.rb b/scenario/service/references2.rb index bcea8db7d..f899dd439 100644 --- a/scenario/service/references2.rb +++ b/scenario/service/references2.rb @@ -1,6 +1,7 @@ ## update: test.rb module M class Foo +# ^[A] def initialize(x) end end @@ -11,7 +12,7 @@ def initialize(x) Foo.new(Foo::C) end -## references: test.rb:2:9 +## references: [A] test.rb:(2,8)-(2,11) test.rb:(6,2)-(6,5) test.rb:(8,2)-(8,5) @@ -23,7 +24,8 @@ def initialize(x) ## update: test.rb Bar = 1 +#^[B] Bar -## references: test.rb:1:1 +## references: [B] test.rb:(2,0)-(2,3) diff --git a/scenario/service/rename.rb b/scenario/service/rename.rb index d0df7ecb3..ae7365c1f 100644 --- a/scenario/service/rename.rb +++ b/scenario/service/rename.rb @@ -4,6 +4,7 @@ def initialize(n) end def foo(n) +# ^[A] end end @@ -11,7 +12,7 @@ def foo(n) Foo.new(1).foo(1.0) Foo.new(1).foo(1.0) -## rename: test.rb:5:7 +## rename: [A] test.rb:(5,6)-(5,9) test.rb:(9,11)-(9,14) test.rb:(10,11)-(10,14) diff --git a/scenario/service/rename2.rb b/scenario/service/rename2.rb index c88bccb8e..126c94797 100644 --- a/scenario/service/rename2.rb +++ b/scenario/service/rename2.rb @@ -1,5 +1,6 @@ ## update: test.rb class Foo +# ^[A] end Foo = 1 @@ -7,7 +8,7 @@ class Foo Foo Foo + Foo -## rename: test.rb:1:7 +## rename: [A] test.rb:(1,6)-(1,9) test.rb:(6,0)-(6,3) test.rb:(7,0)-(7,3) diff --git a/scenario/service/rename3.rb b/scenario/service/rename3.rb index 1a9b0cbf2..0fccb8303 100644 --- a/scenario/service/rename3.rb +++ b/scenario/service/rename3.rb @@ -6,15 +6,16 @@ class Bar end p Foo::Bar::Baz +# ^[A] ^[B]^[C] -## rename: test.rb:7:3 +## rename: [A] test.rb:(1,6)-(1,9) test.rb:(7,2)-(7,5) -## rename: test.rb:7:8 +## rename: [B] test.rb:(2,8)-(2,11) test.rb:(7,7)-(7,10) -## rename: test.rb:7:12 +## rename: [C] test.rb:(3,4)-(3,7) test.rb:(7,12)-(7,15) diff --git a/scenario/service/rename4.rb b/scenario/service/rename4.rb index baeadb411..0042894c8 100644 --- a/scenario/service/rename4.rb +++ b/scenario/service/rename4.rb @@ -1,14 +1,16 @@ ## update: test.rb Foo = 1 +#^[B] Foo +#^[A] -## rename: test.rb:2:1 +## rename: [A] test.rb:(1,0)-(1,3) test.rb:(2,0)-(2,3) -## rename: test.rb:1:1 +## rename: [B] test.rb:(1,0)-(1,3) test.rb:(2,0)-(2,3) -## hover: test.rb:1:1 +## hover: [B] Integer diff --git a/test/scenario_compiler.rb b/test/scenario_compiler.rb index a19a8fb3c..29a634750 100644 --- a/test/scenario_compiler.rb +++ b/test/scenario_compiler.rb @@ -30,7 +30,52 @@ def footer END end + def scan_markers(line, markers, file, line_count, scenario) + pos = 0 + while md = line.match(/(\^+)\[(\w+)\]/, pos) + carets = md[1] + name = md[2] + raise "duplicate marker: [#{name}] in #{scenario}" if markers[name] + col_start = md.begin(1) + col_end = col_start + carets.length + markers[name] = { + file: file, + line: line_count, + col_start: col_start, + col_end: col_end, + point: carets.length == 1, + } + pos = md.end(0) + end + end + + def resolve_marker_for_directive(markers, marker_name) + marker = markers[marker_name] + raise "undefined marker: [#{marker_name}]" unless marker + @file = marker[:file] + @pos = [marker[:line], marker[:col_start]] + end + + def substitute_markers_in_output(line, markers) + line.gsub(/\[(\w+)\]/) do + name = $1 + marker = markers[name] + if marker + if marker[:point] + "(#{marker[:line]},#{marker[:col_start]})" + else + "(#{marker[:line]},#{marker[:col_start]})-(#{marker[:line]},#{marker[:col_end]})" + end + else + $& + end + end + end + def compile_scenario(scenario) + markers = {} + marker_file = "test.rb" + content_line_count = 0 @file = "test.rb" @pos = [0, 0] out = %(eval(<<'TYPEPROF_SCENARIO_END', nil, #{ scenario.dump }, 1)\n) @@ -40,29 +85,49 @@ def compile_scenario(scenario) end close_str = "" need_comment_removal = false + current_command = nil + marker_continuation = false File.foreach(scenario, chomp: true, encoding: "UTF-8") do |line| + if marker_continuation + marker_continuation = false + scan_markers(line, markers, marker_file, content_line_count, scenario) + next + end if line =~ /\A##\s*/ out << close_str - if line =~ /\A##\s*(\w+)(?::\s*(.*?)(?::(\d+)(?::(\d+))?)?)?\z/ + content_line_count = 0 + if line =~ /\A##\s*(\w+)(?::\s*(?:\[(\w+)\]|(.*?)(?::(\d+)(?::(\d+))?)?))?\z/ + current_command = $1 if $2 - @file = $2 - @pos = [$3.to_i, $4.to_i] + resolve_marker_for_directive(markers, $2) + elsif $3 + marker_file = $3 + @file = $3 + @pos = [$4.to_i, $5.to_i] end - ary = send("handle_#{ $1 }").lines.map {|s| s.strip }.join(";").split("DATA") + ary = send("handle_#{ current_command }").lines.map {|s| s.strip }.join(";").split("DATA") raise if ary.size != 2 open_str, close_str = ary out << open_str.chomp close_str += "\n" - need_comment_removal = true if $1 == "definition" + need_comment_removal = true if current_command == "definition" else raise "unknown directive: #{ line.inspect }" end + elsif line =~ /\A#\\\s*\z/ + marker_continuation = true + elsif line =~ /\A#[\s\^]*(?:\^+\[\w+\][\s\^]*)+\z/ + scan_markers(line, markers, marker_file, content_line_count, scenario) else + content_line_count += 1 raise "NUL found" if line.include?("\0") if need_comment_removal line = line.gsub(/#\s*.*\Z/, "") need_comment_removal = false end + if current_command && !%w[update assert assert_without_validation].include?(current_command) && !markers.empty? + line = substitute_markers_in_output(line, markers) + end out << line.gsub("\\") { "\\\\" } << "\n" end end