Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Handle method with only empty lines or comments inside #179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## HEAD (unreleased)

- Output improvement: Handle methods with only newlines or comments in them (https://github.com/ruby/syntax_suggest/pull/179)
- No longer shows the detail of monkey patch as the document (https://github.com/ruby/syntax_suggest/pull/174)
- Drop CI for Ruby 3.2.0-rc1, now that 3.2.0 is available (https://github.com/ruby/syntax_suggest/pull/172)

Expand Down
4 changes: 3 additions & 1 deletion lib/syntax_suggest/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ def self.record_dir(dir)
dir = Pathname(dir)
dir.join(time).tap { |path|
path.mkpath
FileUtils.ln_sf(time, dir.join("last"))
alias_dir = dir.join("last")
FileUtils.rm_rf(alias_dir) if alias_dir.exist?
FileUtils.ln_sf(time, alias_dir)
}
end

Expand Down
191 changes: 172 additions & 19 deletions lib/syntax_suggest/around_block_scan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,36 +38,64 @@ def initialize(code_lines:, block:)
@before_array = []
@stop_after_kw = false

@skip_hidden = false
@skip_empty = false
end

def skip(name)
case name
when :hidden?
@skip_hidden = true
when :empty?
@skip_empty = true
else
raise "Unsupported skip #{name}"
end
@force_add_hidden = false
@force_add_empty = false
end

# When using this flag, `scan_while` will
# bypass the block it's given and always add a
# line that responds truthy to `CodeLine#hidden?`
#
# Lines are hidden when they've been evaluated by
# the parser as part of a block and found to contain
# valid code.
def force_add_hidden
@force_add_hidden = true
self
end

# When using this flag, `scan_while` will
# bypass the block it's given and always add a
# line that responds truthy to `CodeLine#empty?`
#
# Empty lines contain no code, only whitespace such
# as leading spaces a newline.
def force_add_empty
@force_add_empty = true
self
end

# Tells `scan_while` to look for mismatched keyword/end-s
#
# When scanning up, if we see more keywords then end-s it will
# stop. This might happen when scanning outside of a method body.
# the first scan line up would be a keyword and this setting would
# trigger a stop.
#
# When scanning down, stop if there are more end-s than keywords.
def stop_after_kw
@stop_after_kw = true
self
end

# Main work method
#
# The scan_while method takes a block that yields lines above and
# below the block. If the yield returns true, the @before_index
# or @after_index are modified to include the matched line.
#
# In addition to yielding individual lines, the internals of this
# object give a mini DSL to handle common situations such as
# stopping if we've found a keyword/end mis-match in one direction
# or the other.
def scan_while
stop_next = false

kw_count = 0
end_count = 0
index = before_lines.reverse_each.take_while do |line|
next false if stop_next
next true if @skip_hidden && line.hidden?
next true if @skip_empty && line.empty?
next true if @force_add_hidden && line.hidden?
next true if @force_add_empty && line.empty?

kw_count += 1 if line.is_kw?
end_count += 1 if line.is_end?
Expand All @@ -87,8 +115,8 @@ def scan_while
end_count = 0
index = after_lines.take_while do |line|
next false if stop_next
next true if @skip_hidden && line.hidden?
next true if @skip_empty && line.empty?
next true if @force_add_hidden && line.hidden?
next true if @force_add_empty && line.empty?

kw_count += 1 if line.is_kw?
end_count += 1 if line.is_end?
Expand All @@ -105,6 +133,33 @@ def scan_while
self
end

# Shows surrounding kw/end pairs
#
# The purpose of showing these extra pairs is due to cases
# of ambiguity when only one visible line is matched.
#
# For example:
#
# 1 class Dog
# 2 def bark
# 4 def eat
# 5 end
# 6 end
#
# In this case either line 2 could be missing an `end` or
# line 4 was an extra line added by mistake (it happens).
#
# When we detect the above problem it shows the issue
# as only being on line 2
#
# 2 def bark
#
# Showing "neighbor" keyword pairs gives extra context:
#
# 2 def bark
# 4 def eat
# 5 end
#
def capture_neighbor_context
lines = []
kw_count = 0
Expand Down Expand Up @@ -146,6 +201,20 @@ def capture_neighbor_context
lines
end

# Shows the context around code provided by "falling" indentation
#
# Converts:
#
# it "foo" do
#
# into:
#
# class OH
# def hello
# it "foo" do
# end
# end
#
def on_falling_indent
last_indent = @orig_indent
before_lines.reverse_each do |line|
Expand All @@ -166,18 +235,79 @@ def on_falling_indent
end
end

def scan_neighbors
# Scanning is intentionally conservative because
# we have no way of rolling back an agressive block (at this time)
#
# If a block was stopped for some trivial reason, (like an empty line)
# but the next line would have caused it to be balanced then we
# can check that condition and grab just one more line either up or
# down.
#
# For example, below if we're scanning up, line 2 might cause
# the scanning to stop. This is because empty lines might
# denote logical breaks where the user intended to chunk code
# which is a good place to stop and check validity. Unfortunately
# it also means we might have a "dangling" keyword or end.
#
# 1 def bark
# 2
# 3 end
#
# If lines 2 and 3 are in the block, then when this method is
# run it would see it is unbalanced, but that acquiring line 1
# would make it balanced, so that's what it does.
def lookahead_balance_one_line
kw_count = 0
end_count = 0
lines.each do |line|
kw_count += 1 if line.is_kw?
end_count += 1 if line.is_end?
end

return self if kw_count == end_count # nothing to balance

# More ends than keywords, check if we can balance expanding up
if (end_count - kw_count) == 1 && next_up
return self unless next_up.is_kw?
return self unless next_up.indent >= @orig_indent

@before_index = next_up.index

# More keywords than ends, check if we can balance by expanding down
elsif (kw_count - end_count) == 1 && next_down
return self unless next_down.is_end?
return self unless next_down.indent >= @orig_indent

@after_index = next_down.index
end
self
end

# Finds code lines at the same or greater indentation and adds them
# to the block
def scan_neighbors_not_empty
scan_while { |line| line.not_empty? && line.indent >= @orig_indent }
end

# Returns the next line to be scanned above the current block.
# Returns `nil` if at the top of the document already
def next_up
@code_lines[before_index.pred]
end

# Returns the next line to be scanned below the current block.
# Returns `nil` if at the bottom of the document already
def next_down
@code_lines[after_index.next]
end

# Scan blocks based on indentation of next line above/below block
#
# Determines indentaion of the next line above/below the current block.
#
# Normally this is called when a block has expanded to capture all "neighbors"
# at the same (or greater) indentation and needs to expand out. For example
# the `def/end` lines surrounding a method.
def scan_adjacent_indent
before_after_indent = []
before_after_indent << (next_up&.indent || 0)
Expand All @@ -189,6 +319,16 @@ def scan_adjacent_indent
self
end

# TODO: Doc or delete
#
# I don't remember why this is needed, but it's called in code_context.
# It's related to the implementation of `capture_neighbor_context` somehow
# and that display improvement is only triggered when there's one visible line
#
# I think the primary purpose is to not include the current line in the
# logic evaluation of `capture_neighbor_context`. If that's true, then
# we should fix that method to handle this logic instead of only using
# it in one place and together.
def start_at_next_line
before_index
after_index
Expand All @@ -197,26 +337,39 @@ def start_at_next_line
self
end

# Return the currently matched lines as a `CodeBlock`
#
# When a `CodeBlock` is created it will gather metadata about
# itself, so this is not a free conversion. Avoid allocating
# more CodeBlock's than needed
def code_block
CodeBlock.new(lines: lines)
end

# Returns the lines matched by the current scan as an
# array of CodeLines
def lines
@code_lines[before_index..after_index]
end

# Gives the index of the first line currently scanned
def before_index
@before_index ||= @orig_before_index
end

# Gives the index of the last line currently scanned
def after_index
@after_index ||= @orig_after_index
end

# Returns an array of all the CodeLines that exist before
# the currently scanned block
private def before_lines
@code_lines[0...before_index] || []
end

# Returns an array of all the CodeLines that exist after
# the currently scanned block
private def after_lines
@code_lines[after_index.next..-1] || []
end
Expand Down
Loading