class SyntaxSuggest::CleanDocument
Parst und bereinigt Quellcode zu einem lexikalisch bewussten Dokument
Intern wird das Dokument durch ein Array dargestellt, wobei jeder Index einen CodeLine enthält, der einer Zeile aus dem Quellcode entspricht.
Es gibt drei Hauptphasen im Algorithmus
-
Bereinigung/Formatierung des Eingabequellcodes
-
Suche nach ungültigen Blöcken
-
Formatierung ungültiger Blöcke in etwas Sinnvolles
Diese Klasse kümmert sich um den ersten Teil.
Der Grund für die Existenz dieser Klasse ist die Formatierung des Eingabequellcodes für eine bessere/einfachere/sauberere Erkundung.
Die Klasse CodeSearch arbeitet auf Zeilenebene, daher müssen wir vorsichtig sein, keine Zeilen einzufügen, die für sich genommen gültig aussehen, aber beim Entfernen Syntaxfehler oder seltsames Verhalten auslösen.
## Anhängige Schrägstriche verbinden
Code mit einem anhängigen Schrägstrich wird logisch als eine einzige Zeile behandelt
1 it "code can be split" \ 2 "across multiple lines" do
In diesem Fall würde das Entfernen von Zeile 2 einen Syntaxfehler verursachen. Wir umgehen dies, indem wir die beiden Zeilen intern zu einem einzigen "Zeilen"-Objekt verbinden
## Logisch aufeinanderfolgende Zeilen
Code, der über mehrere Zeilen aufgeteilt werden kann, wie z. B. Methodenaufrufe, befindet sich in verschiedenen Zeilen
1 User. 2 where(name: "schneems"). 3 first
Das Entfernen von Zeile 2 kann einen Syntaxfehler verursachen. Um dies zu beheben, werden alle Zeilen zu einer zusammengefügt.
## Heredocs
Ein Heredoc ist eine Möglichkeit, eine mehrzeilige Zeichenkette zu definieren. Sie können viele Probleme verursachen. Wenn sie als einzelne Zeile belassen werden, versucht der Parser, den Inhalt als Ruby-Code und nicht als Zeichenkette zu parsen. Selbst ohne dieses Problem stoßen wir auf ein Problem mit der Einrückung
1 foo = <<~HEREDOC 2 "Be yourself; everyone else is already taken."" 3 ― Oscar Wilde 4 puts "I look like ruby code" # but i'm still a heredoc 5 HEREDOC
Wenn wir diese Zeilen nicht verbinden würden, würde unser Algorithmus denken, dass Zeile 4 von den anderen getrennt ist, eine höhere Einrückung hat, sie zuerst betrachtet und entfernt.
Wenn der Code Zeile 5 isoliert auswertet, wird er Zeile 5 als Konstante betrachten, sie entfernen und einen Syntaxfehler einführen.
All diese Probleme werden durch das Zusammenfügen des gesamten Heredocs zu einer einzigen Zeile behoben.
## Kommentare und Leerzeichen
Kommentare können die Art und Weise, wie der Lexer uns mitteilt, dass die Zeile logisch zur nächsten Zeile gehört, durcheinanderbringen. Dies ist gültiger Ruby-Code, führt aber zu einer anderen Lex-Ausgabe als zuvor
1 User. 2 where(name: "schneems"). 3 # Comment here 4 first
Um dies zu handhaben, können wir Kommentarzeilen durch leere Zeilen ersetzen und dann den Quellcode neu lexen. Dieses Entfernen und Neuluxen bewahrt den Zeilenindex und die Dokumentgröße, erzeugt aber ein leichter zu handhabendes Dokument.
Öffentliche Klassenmethoden
Source
# File lib/syntax_suggest/clean_document.rb, line 87 def initialize(source:) lines = clean_sweep(source: source) @document = CodeLine.from_source(lines.join, lines: lines) end
Öffentliche Instanzmethoden
Source
# File lib/syntax_suggest/clean_document.rb, line 94 def call join_trailing_slash! join_consecutive! join_heredoc! self end
Ruft alle "Cleaner" des Dokuments auf und gibt self zurück
Source
# File lib/syntax_suggest/clean_document.rb, line 157 def clean_sweep(source:) # Match comments, but not HEREDOC strings with #{variable} interpolation # https://rubular.com/r/HPwtW9OYxKUHXQ source.lines.map do |line| if line.match?(/^\s*#([^{].*|)$/) $/ else line end end end
Entfernt Kommentare
Ersetzt sie durch leere neue Zeilen
source = <<~'EOM'
# Comment 1
puts "hello"
# Comment 2
puts "world"
EOM
lines = CleanDocument.new(source: source).lines
expect(lines[0].to_s).to eq("\n")
expect(lines[1].to_s).to eq("puts "hello")
expect(lines[2].to_s).to eq("\n")
expect(lines[3].to_s).to eq("puts "world")
Wichtig: Dies muss vor dem Lexen erfolgen.
Nachdem diese Änderung vorgenommen wurde, lexen wir das Dokument, da das Entfernen von Kommentaren die Art und Weise ändern kann, wie das Dokument geparst wird.
Zum Beispiel
values = LexAll.new(source: <<~EOM))
User.
# comment
where(name: 'schneems')
EOM
expect(
values.count {|v| v.type == :on_ignored_nl}
).to eq(1)
Nachdem der Kommentar entfernt wurde
values = LexAll.new(source: <<~EOM))
User.
where(name: 'schneems')
EOM
expect(
values.count {|v| v.type == :on_ignored_nl}
).to eq(2)
Source
# File lib/syntax_suggest/clean_document.rb, line 225 def join_consecutive! consecutive_groups = @document.select(&:ignore_newline_not_beg?).map do |code_line| take_while_including(code_line.index..) do |line| line.ignore_newline_not_beg? end end join_groups(consecutive_groups) self end
Fasst logisch "aufeinanderfolgende" Zeilen zusammen
source = <<~'EOM' User. where(name: 'schneems'). first EOM lines = CleanDocument.new(source: source).join_consecutive!.lines expect(lines[0].to_s).to eq(source) expect(lines[1].to_s).to eq("")
Der einzige bekannte Fall, der hier nicht behandelt wird, ist
Ripper.lex <<~EOM a && b || c EOM
Aus irgendeinem Grund führt dies zu "on_ignore_newline", aber mit BEG-Typ
Source
# File lib/syntax_suggest/clean_document.rb, line 266 def join_groups(groups) groups.each do |lines| line = lines.first # Handle the case of multiple groups in a row # if one is already replaced, move on next if @document[line.index].empty? # Join group into the first line @document[line.index] = CodeLine.new( lex: lines.map(&:lex).flatten, line: lines.join, index: line.index ) # Hide the rest of the lines lines[1..].each do |line| # The above lines already have newlines in them, if add more # then there will be double newline, use an empty line instead @document[line.index] = CodeLine.new(line: "", index: line.index, lex: []) end end self end
Hilfsfunktion zum Verbinden von "Gruppen" von Zeilen
Die Eingabe wird als Typ Array<Array<CodeLine>> erwartet
Das äußere Array enthält die verschiedenen "Gruppen", während das innere Array Codezeilen enthält.
Alle Codezeilen werden in die erste Zeile ihrer Gruppe "eingefügt".
Um die Dokumentgröße beizubehalten, werden leere Zeilen anstelle der "eingefügten" Zeilen platziert
Source
# File lib/syntax_suggest/clean_document.rb, line 181 def join_heredoc! start_index_stack = [] heredoc_beg_end_index = [] lines.each do |line| line.lex.each do |lex_value| case lex_value.type when :on_heredoc_beg start_index_stack << line.index when :on_heredoc_end start_index = start_index_stack.pop end_index = line.index heredoc_beg_end_index << [start_index, end_index] end end end heredoc_groups = heredoc_beg_end_index.map { |start_index, end_index| @document[start_index..end_index] } join_groups(heredoc_groups) self end
Fasst alle Heredoc-Zeilen zu einer Zeile zusammen
source = <<~'EOM' foo = <<~HEREDOC lol hehehe HEREDOC EOM lines = CleanDocument.new(source: source).join_heredoc!.lines expect(lines[0].to_s).to eq(source) expect(lines[1].to_s).to eq("")
Source
# File lib/syntax_suggest/clean_document.rb, line 246 def join_trailing_slash! trailing_groups = @document.select(&:trailing_slash?).map do |code_line| take_while_including(code_line.index..) { |x| x.trailing_slash? } end join_groups(trailing_groups) self end
Verbindet Zeilen mit einem anhängigen Schrägstrich
source = <<~'EOM' it "code can be split" \ "across multiple lines" do EOM lines = CleanDocument.new(source: source).join_consecutive!.lines expect(lines[0].to_s).to eq(source) expect(lines[1].to_s).to eq("")
Source
# File lib/syntax_suggest/clean_document.rb, line 104 def lines @document end
Gibt ein Array von CodeLines im Dokument zurück
Source
# File lib/syntax_suggest/clean_document.rb, line 296 def take_while_including(range = 0..) take_next_and_stop = false @document[range].take_while do |line| next if take_next_and_stop take_next_and_stop = !(yield line) true end end
Hilfsfunktion zum Abrufen von Elementen aus dem Dokument
Ähnlich wie 'take_while', außer dass beim Beenden der Iteration auch die Zeile zurückgegeben wird, die zum Stoppen geführt hat
Source
# File lib/syntax_suggest/clean_document.rb, line 109 def to_s @document.join end
Rendert das Dokument zurück in eine Zeichenkette