類別 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