类 SyntaxSuggest::CaptureCodeContext
将“无效块”转换为有用的上下文
算法有三个主要阶段
-
清理/格式化输入源
-
搜索无效块
-
将无效块格式化为有意义的内容
此类处理第三部分。
该算法非常擅长在步骤 2 中将单个块中的所有语法错误捕获在一个块中,但结果可能包含歧义。人类擅长模式匹配和过滤,可以在心理上删除无关数据,但他们无法添加不存在的额外数据。
在已知歧义的情况下,此类会将上下文添加回歧义,以便程序员拥有完整的信息。
除了处理这些歧义之外,它还捕获周围代码上下文信息
puts block.to_s # => "def bark" context = CaptureCodeContext.new( blocks: block, code_lines: code_lines ) lines = context.call.map(&:original) puts lines.join # => class Dog def bark end
属性
公共类方法
# File lib/syntax_suggest/capture_code_context.rb, line 51 def initialize(blocks:, code_lines:) @blocks = Array(blocks) @code_lines = code_lines @visible_lines = @blocks.map(&:visible_lines).flatten @lines_to_output = @visible_lines.dup end
公共实例方法
# File lib/syntax_suggest/capture_code_context.rb, line 58 def call @blocks.each do |block| capture_first_kw_end_same_indent(block) capture_last_end_same_indent(block) capture_before_after_kws(block) capture_falling_indent(block) end sorted_lines end
显示周围的 kw/end 对
显示这些额外对的目的是为了解决仅匹配一行时出现的歧义情况。
例如
1 class Dog 2 def bark 4 def eat 5 end 6 end
在这种情况下,第 2 行可能缺少一个“end”,或者第 4 行是错误添加的额外行(这种情况很常见)。
当我们检测到上述问题时,它会显示问题仅出现在第 2 行。
2 def bark
显示“邻居”关键字对提供额外的上下文
2 def bark 4 def eat 5 end
# File lib/syntax_suggest/capture_code_context.rb, line 127 def capture_before_after_kws(block) return unless block.visible_lines.count == 1 around_lines = Capture::BeforeAfterKeywordEnds.new( code_lines: @code_lines, block: block ).call around_lines -= block.lines @lines_to_output.concat(around_lines) end
显示由“下降”缩进提供的代码周围的上下文
转换
it "foo" do
成
class OH def hello it "foo" do end end
# File lib/syntax_suggest/capture_code_context.rb, line 91 def capture_falling_indent(block) Capture::FallingIndentLines.new( block: block, code_lines: @code_lines ).call do |line| @lines_to_output << line end end
“capture_last_end_same_indent”的逻辑逆
当存在一个无效块,其中一个“end”缺少一个关键字,紧跟在另一个“end”之后,则不清楚哪个“end”缺少关键字。
以这个例子为例
class Dog # 1 puts "woof" # 2 end # 3 end # 4
问题行将被识别为
> end # 4
这是因为第 1、2 和 3 行在技术上是有效的代码,并且首先被扩展、被认为有效并隐藏。我们需要取消隐藏第 1 行上的匹配关键字。还要反向操作,如果存在不匹配的结束,也将其显示出来
# File lib/syntax_suggest/capture_code_context.rb, line 221 def capture_first_kw_end_same_indent(block) return if block.visible_lines.length != 1 return unless block.visible_lines.first.is_end? visible_line = block.visible_lines.first lines = @code_lines[block.lines.first.index..visible_line.index] matching_kw = lines.reverse.detect { |line| line.indent == block.current_indent && line.is_kw? } return unless matching_kw @lines_to_output << matching_kw kw_count = 0 end_count = 0 orphan_end = @code_lines[matching_kw.index..visible_line.index].detect do |line| kw_count += 1 if line.is_kw? end_count += 1 if line.is_end? end_count >= kw_count end return unless orphan_end @lines_to_output << orphan_end end
当存在一个无效的代码块,其中一个关键字在另一个结束之前缺少结束符时,不清楚哪个关键字缺少结束符
以这个例子为例
class Dog # 1 def bark # 2 puts "woof" # 3 end # 4
但是,由于 github.com/ruby/syntax_suggest/issues/32,问题行将被识别为
> class Dog # 1
因为第 2、3 和 4 行在技术上是有效的代码,并且首先被扩展、被认为有效并隐藏。我们需要取消隐藏第 4 行上的匹配结束符。还要反向操作,如果存在不匹配的关键字,也将其显示出来
# File lib/syntax_suggest/capture_code_context.rb, line 161 def capture_last_end_same_indent(block) return if block.visible_lines.length != 1 return unless block.visible_lines.first.is_kw? visible_line = block.visible_lines.first lines = @code_lines[visible_line.index..block.lines.last.index] # Find first end with same indent # (this would return line 4) # # end # 4 matching_end = lines.detect { |line| line.indent == block.current_indent && line.is_end? } return unless matching_end @lines_to_output << matching_end # Work backwards from the end to # see if there are mis-matched # keyword/end pairs # # Return the first mis-matched keyword # this would find line 2 # # def bark # 2 # puts "woof" # 3 # end # 4 end_count = 0 kw_count = 0 kw_line = @code_lines[visible_line.index..matching_end.index].reverse.detect do |line| end_count += 1 if line.is_end? kw_count += 1 if line.is_kw? !kw_count.zero? && kw_count >= end_count end return unless kw_line @lines_to_output << kw_line end
# File lib/syntax_suggest/capture_code_context.rb, line 69 def sorted_lines @lines_to_output.select!(&:not_empty?) @lines_to_output.uniq! @lines_to_output.sort! @lines_to_output end