summaryrefslogtreecommitdiff
path: root/lib/syntax_suggest/explain_syntax.rb
diff options
context:
space:
mode:
authorschneems <[email protected]>2022-07-26 15:21:09 -0500
committerHiroshi SHIBATA <[email protected]>2022-08-19 10:02:24 +0900
commit490af8dbdb66263f29d0b4e43752fbb298b94862 (patch)
tree5f161e99d27a1417f446e8b1516263fd76d6f0bc /lib/syntax_suggest/explain_syntax.rb
parenta50df1ab0eb312e5cdcf010d2c1b362ec41f3c59 (diff)
Sync SyntaxSuggest
``` $ tool/sync_default_gems.rb syntax_suggest ```
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/5859
Diffstat (limited to 'lib/syntax_suggest/explain_syntax.rb')
-rw-r--r--lib/syntax_suggest/explain_syntax.rb103
1 files changed, 103 insertions, 0 deletions
diff --git a/lib/syntax_suggest/explain_syntax.rb b/lib/syntax_suggest/explain_syntax.rb
new file mode 100644
index 0000000000..142ed2e269
--- /dev/null
+++ b/lib/syntax_suggest/explain_syntax.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require_relative "left_right_lex_count"
+
+module SyntaxSuggest
+ # Explains syntax errors based on their source
+ #
+ # example:
+ #
+ # source = "def foo; puts 'lol'" # Note missing end
+ # explain ExplainSyntax.new(
+ # code_lines: CodeLine.from_source(source)
+ # ).call
+ # explain.errors.first
+ # # => "Unmatched keyword, missing `end' ?"
+ #
+ # When the error cannot be determined by lexical counting
+ # then ripper is run against the input and the raw ripper
+ # errors returned.
+ #
+ # Example:
+ #
+ # source = "1 * " # Note missing a second number
+ # explain ExplainSyntax.new(
+ # code_lines: CodeLine.from_source(source)
+ # ).call
+ # explain.errors.first
+ # # => "syntax error, unexpected end-of-input"
+ class ExplainSyntax
+ INVERSE = {
+ "{" => "}",
+ "}" => "{",
+ "[" => "]",
+ "]" => "[",
+ "(" => ")",
+ ")" => "(",
+ "|" => "|"
+ }.freeze
+
+ def initialize(code_lines:)
+ @code_lines = code_lines
+ @left_right = LeftRightLexCount.new
+ @missing = nil
+ end
+
+ def call
+ @code_lines.each do |line|
+ line.lex.each do |lex|
+ @left_right.count_lex(lex)
+ end
+ end
+
+ self
+ end
+
+ # Returns an array of missing elements
+ #
+ # For example this:
+ #
+ # ExplainSyntax.new(code_lines: lines).missing
+ # # => ["}"]
+ #
+ # Would indicate that the source is missing
+ # a `}` character in the source code
+ def missing
+ @missing ||= @left_right.missing
+ end
+
+ # Converts a missing string to
+ # an human understandable explanation.
+ #
+ # Example:
+ #
+ # explain.why("}")
+ # # => "Unmatched `{', missing `}' ?"
+ #
+ def why(miss)
+ case miss
+ when "keyword"
+ "Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?"
+ when "end"
+ "Unmatched keyword, missing `end' ?"
+ else
+ inverse = INVERSE.fetch(miss) {
+ raise "Unknown explain syntax char or key: #{miss.inspect}"
+ }
+ "Unmatched `#{inverse}', missing `#{miss}' ?"
+ end
+ end
+
+ # Returns an array of syntax error messages
+ #
+ # If no missing pairs are found it falls back
+ # on the original ripper error messages
+ def errors
+ if missing.empty?
+ return RipperErrors.new(@code_lines.map(&:original).join).call.errors
+ end
+
+ missing.map { |miss| why(miss) }
+ end
+ end
+end