syntax_suggest 1.0.1 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/check_changelog.yml +1 -1
- data/.github/workflows/ci.yml +4 -4
- data/.rspec +0 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +13 -2
- data/bin/rake +6 -0
- data/bin/rspec +6 -0
- data/lib/syntax_suggest/api.rb +3 -1
- data/lib/syntax_suggest/around_block_scan.rb +172 -19
- data/lib/syntax_suggest/block_expand.rb +93 -8
- data/lib/syntax_suggest/capture_code_context.rb +0 -1
- data/lib/syntax_suggest/clean_document.rb +4 -2
- data/lib/syntax_suggest/code_line.rb +3 -5
- data/lib/syntax_suggest/core_ext.rb +49 -38
- data/lib/syntax_suggest/parse_blocks_from_indent_line.rb +2 -2
- data/lib/syntax_suggest/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20590130dde6c0a40a17b7d01ad9fca063c7044c83d7f04609f9d75df564f9ce
|
4
|
+
data.tar.gz: 73bed388644be5a7a71fe5ab892c8925d50f9dbcad5d918ca07512ffd85188af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42a28115779d75b9ff89e58b2a26a9c4807e834a91f8671ffdb2725f9dea954b017310c3b67a9bfe7b97ea6bdbb28e84c59f8bf5c951b0692e99f74ab3e9a5a3
|
7
|
+
data.tar.gz: 51c04529ff19c2181b7f025ff04165f436afaf22e7c9daffb23767176b48b8216006a9fd669b96a435abd8c9886b046224212e9f6c50435008a8ef677477f771
|
@@ -13,7 +13,7 @@ jobs:
|
|
13
13
|
!contains(github.event.pull_request.body, '[skip ci]') &&
|
14
14
|
!contains(github.event.pull_request.labels.*.name, 'skip changelog')
|
15
15
|
steps:
|
16
|
-
- uses: actions/checkout@
|
16
|
+
- uses: actions/checkout@v3.3.0
|
17
17
|
- name: Check that CHANGELOG is touched
|
18
18
|
run: |
|
19
19
|
git fetch origin ${{ github.base_ref }} --depth 1 && \
|
data/.github/workflows/ci.yml
CHANGED
@@ -9,7 +9,7 @@ jobs:
|
|
9
9
|
runs-on: ubuntu-latest
|
10
10
|
steps:
|
11
11
|
- name: Checkout code
|
12
|
-
uses: actions/checkout@v3
|
12
|
+
uses: actions/checkout@v3.3.0
|
13
13
|
- name: Set up Ruby
|
14
14
|
uses: ruby/setup-ruby@v1
|
15
15
|
with:
|
@@ -29,16 +29,16 @@ jobs:
|
|
29
29
|
- 2.7
|
30
30
|
- '3.0'
|
31
31
|
- 3.1
|
32
|
-
-
|
32
|
+
- 3.2
|
33
33
|
- head
|
34
34
|
steps:
|
35
35
|
- name: Checkout code
|
36
|
-
uses: actions/checkout@v3
|
36
|
+
uses: actions/checkout@v3.3.0
|
37
37
|
- name: Set up Ruby
|
38
38
|
uses: ruby/setup-ruby@v1
|
39
39
|
with:
|
40
40
|
ruby-version: ${{ matrix.ruby }}
|
41
41
|
bundler-cache: true
|
42
42
|
- name: test
|
43
|
-
run:
|
43
|
+
run: bin/rake test
|
44
44
|
continue-on-error: ${{ matrix.ruby == 'head' }}
|
data/.rspec
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
## HEAD (unreleased)
|
2
2
|
|
3
|
+
## 1.0.3
|
4
|
+
|
5
|
+
- Output improvement: Handle methods with only newlines or comments in them (https://github.com/ruby/syntax_suggest/pull/179)
|
6
|
+
- No longer shows the detail of monkey patch as the document (https://github.com/ruby/syntax_suggest/pull/174)
|
7
|
+
- Drop CI for Ruby 3.2.0-rc1, now that 3.2.0 is available (https://github.com/ruby/syntax_suggest/pull/172)
|
8
|
+
|
9
|
+
## 1.0.2
|
10
|
+
|
11
|
+
- Drop support for Ruby 3.2.0 preview, now that 3.2.0-rc1 is available (https://github.com/ruby/syntax_suggest/pull/165)
|
12
|
+
- Native support of `SyntaxError#path`, support 3.2.0-preview3 will be dropped with the release of 3.2.0-preview4 (https://github.com/ruby/syntax_suggest/pull/164)
|
13
|
+
- Added dependabot for GitHub Actions (https://github.com/ruby/syntax_suggest/pull/160)
|
14
|
+
|
3
15
|
## 1.0.1
|
4
16
|
|
5
17
|
- Replace `❯` with `>` in error output for compatability with more fonts (https://github.com/ruby/syntax_suggest/pull/161)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -180,9 +180,20 @@ Any other entrypoints are subject to change without warning. If you want to use
|
|
180
180
|
|
181
181
|
## Development
|
182
182
|
|
183
|
-
|
183
|
+
### Handling conflicts with the default gem
|
184
184
|
|
185
|
-
|
185
|
+
Because `syntax_suggest` is a default gem you can get conflicts when working on this project with Ruby 3.2+. To fix conflicts you can disable loading `syntax_suggest` as a default gem by using then environment variable `RUBYOPT` with the value `--disable=syntax_suggest`. The `RUBYOPT` environment variable works the same as if we had entered those flags directly in the ruby cli (i.e. `ruby --disable=syntax_suggest` is the same as `RUBYOPT="--disable=syntax_suggest" ruby`). It's needed because we don't always directly execute Ruby and RUBYOPT will be picked up when other commands load ruby (`rspec`, `rake`, or `bundle` etc.).
|
186
|
+
|
187
|
+
There are some binstubs that already have this done for you. Instead of running `bundle exec rake` you can run `bin/rake`. Binstubs provided:
|
188
|
+
|
189
|
+
- `bin/rake`
|
190
|
+
- `bin/rspec`
|
191
|
+
|
192
|
+
### Installation
|
193
|
+
|
194
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
195
|
+
|
196
|
+
To install this gem onto your local machine, run `bin/rake install`. To release a new version, update the version number in `version.rb`, and then run `bin/rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
186
197
|
|
187
198
|
### How to debug changes to output display
|
188
199
|
|
data/bin/rake
ADDED
data/bin/rspec
ADDED
data/lib/syntax_suggest/api.rb
CHANGED
@@ -91,7 +91,9 @@ module SyntaxSuggest
|
|
91
91
|
dir = Pathname(dir)
|
92
92
|
dir.join(time).tap { |path|
|
93
93
|
path.mkpath
|
94
|
-
|
94
|
+
alias_dir = dir.join("last")
|
95
|
+
FileUtils.rm_rf(alias_dir) if alias_dir.exist?
|
96
|
+
FileUtils.ln_sf(time, alias_dir)
|
95
97
|
}
|
96
98
|
end
|
97
99
|
|
@@ -38,36 +38,64 @@ module SyntaxSuggest
|
|
38
38
|
@before_array = []
|
39
39
|
@stop_after_kw = false
|
40
40
|
|
41
|
-
@
|
42
|
-
@
|
43
|
-
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
41
|
+
@force_add_hidden = false
|
42
|
+
@force_add_empty = false
|
43
|
+
end
|
44
|
+
|
45
|
+
# When using this flag, `scan_while` will
|
46
|
+
# bypass the block it's given and always add a
|
47
|
+
# line that responds truthy to `CodeLine#hidden?`
|
48
|
+
#
|
49
|
+
# Lines are hidden when they've been evaluated by
|
50
|
+
# the parser as part of a block and found to contain
|
51
|
+
# valid code.
|
52
|
+
def force_add_hidden
|
53
|
+
@force_add_hidden = true
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# When using this flag, `scan_while` will
|
58
|
+
# bypass the block it's given and always add a
|
59
|
+
# line that responds truthy to `CodeLine#empty?`
|
60
|
+
#
|
61
|
+
# Empty lines contain no code, only whitespace such
|
62
|
+
# as leading spaces a newline.
|
63
|
+
def force_add_empty
|
64
|
+
@force_add_empty = true
|
54
65
|
self
|
55
66
|
end
|
56
67
|
|
68
|
+
# Tells `scan_while` to look for mismatched keyword/end-s
|
69
|
+
#
|
70
|
+
# When scanning up, if we see more keywords then end-s it will
|
71
|
+
# stop. This might happen when scanning outside of a method body.
|
72
|
+
# the first scan line up would be a keyword and this setting would
|
73
|
+
# trigger a stop.
|
74
|
+
#
|
75
|
+
# When scanning down, stop if there are more end-s than keywords.
|
57
76
|
def stop_after_kw
|
58
77
|
@stop_after_kw = true
|
59
78
|
self
|
60
79
|
end
|
61
80
|
|
81
|
+
# Main work method
|
82
|
+
#
|
83
|
+
# The scan_while method takes a block that yields lines above and
|
84
|
+
# below the block. If the yield returns true, the @before_index
|
85
|
+
# or @after_index are modified to include the matched line.
|
86
|
+
#
|
87
|
+
# In addition to yielding individual lines, the internals of this
|
88
|
+
# object give a mini DSL to handle common situations such as
|
89
|
+
# stopping if we've found a keyword/end mis-match in one direction
|
90
|
+
# or the other.
|
62
91
|
def scan_while
|
63
92
|
stop_next = false
|
64
|
-
|
65
93
|
kw_count = 0
|
66
94
|
end_count = 0
|
67
95
|
index = before_lines.reverse_each.take_while do |line|
|
68
96
|
next false if stop_next
|
69
|
-
next true if @
|
70
|
-
next true if @
|
97
|
+
next true if @force_add_hidden && line.hidden?
|
98
|
+
next true if @force_add_empty && line.empty?
|
71
99
|
|
72
100
|
kw_count += 1 if line.is_kw?
|
73
101
|
end_count += 1 if line.is_end?
|
@@ -87,8 +115,8 @@ module SyntaxSuggest
|
|
87
115
|
end_count = 0
|
88
116
|
index = after_lines.take_while do |line|
|
89
117
|
next false if stop_next
|
90
|
-
next true if @
|
91
|
-
next true if @
|
118
|
+
next true if @force_add_hidden && line.hidden?
|
119
|
+
next true if @force_add_empty && line.empty?
|
92
120
|
|
93
121
|
kw_count += 1 if line.is_kw?
|
94
122
|
end_count += 1 if line.is_end?
|
@@ -105,6 +133,33 @@ module SyntaxSuggest
|
|
105
133
|
self
|
106
134
|
end
|
107
135
|
|
136
|
+
# Shows surrounding kw/end pairs
|
137
|
+
#
|
138
|
+
# The purpose of showing these extra pairs is due to cases
|
139
|
+
# of ambiguity when only one visible line is matched.
|
140
|
+
#
|
141
|
+
# For example:
|
142
|
+
#
|
143
|
+
# 1 class Dog
|
144
|
+
# 2 def bark
|
145
|
+
# 4 def eat
|
146
|
+
# 5 end
|
147
|
+
# 6 end
|
148
|
+
#
|
149
|
+
# In this case either line 2 could be missing an `end` or
|
150
|
+
# line 4 was an extra line added by mistake (it happens).
|
151
|
+
#
|
152
|
+
# When we detect the above problem it shows the issue
|
153
|
+
# as only being on line 2
|
154
|
+
#
|
155
|
+
# 2 def bark
|
156
|
+
#
|
157
|
+
# Showing "neighbor" keyword pairs gives extra context:
|
158
|
+
#
|
159
|
+
# 2 def bark
|
160
|
+
# 4 def eat
|
161
|
+
# 5 end
|
162
|
+
#
|
108
163
|
def capture_neighbor_context
|
109
164
|
lines = []
|
110
165
|
kw_count = 0
|
@@ -146,6 +201,20 @@ module SyntaxSuggest
|
|
146
201
|
lines
|
147
202
|
end
|
148
203
|
|
204
|
+
# Shows the context around code provided by "falling" indentation
|
205
|
+
#
|
206
|
+
# Converts:
|
207
|
+
#
|
208
|
+
# it "foo" do
|
209
|
+
#
|
210
|
+
# into:
|
211
|
+
#
|
212
|
+
# class OH
|
213
|
+
# def hello
|
214
|
+
# it "foo" do
|
215
|
+
# end
|
216
|
+
# end
|
217
|
+
#
|
149
218
|
def on_falling_indent
|
150
219
|
last_indent = @orig_indent
|
151
220
|
before_lines.reverse_each do |line|
|
@@ -166,18 +235,79 @@ module SyntaxSuggest
|
|
166
235
|
end
|
167
236
|
end
|
168
237
|
|
169
|
-
|
238
|
+
# Scanning is intentionally conservative because
|
239
|
+
# we have no way of rolling back an agressive block (at this time)
|
240
|
+
#
|
241
|
+
# If a block was stopped for some trivial reason, (like an empty line)
|
242
|
+
# but the next line would have caused it to be balanced then we
|
243
|
+
# can check that condition and grab just one more line either up or
|
244
|
+
# down.
|
245
|
+
#
|
246
|
+
# For example, below if we're scanning up, line 2 might cause
|
247
|
+
# the scanning to stop. This is because empty lines might
|
248
|
+
# denote logical breaks where the user intended to chunk code
|
249
|
+
# which is a good place to stop and check validity. Unfortunately
|
250
|
+
# it also means we might have a "dangling" keyword or end.
|
251
|
+
#
|
252
|
+
# 1 def bark
|
253
|
+
# 2
|
254
|
+
# 3 end
|
255
|
+
#
|
256
|
+
# If lines 2 and 3 are in the block, then when this method is
|
257
|
+
# run it would see it is unbalanced, but that acquiring line 1
|
258
|
+
# would make it balanced, so that's what it does.
|
259
|
+
def lookahead_balance_one_line
|
260
|
+
kw_count = 0
|
261
|
+
end_count = 0
|
262
|
+
lines.each do |line|
|
263
|
+
kw_count += 1 if line.is_kw?
|
264
|
+
end_count += 1 if line.is_end?
|
265
|
+
end
|
266
|
+
|
267
|
+
return self if kw_count == end_count # nothing to balance
|
268
|
+
|
269
|
+
# More ends than keywords, check if we can balance expanding up
|
270
|
+
if (end_count - kw_count) == 1 && next_up
|
271
|
+
return self unless next_up.is_kw?
|
272
|
+
return self unless next_up.indent >= @orig_indent
|
273
|
+
|
274
|
+
@before_index = next_up.index
|
275
|
+
|
276
|
+
# More keywords than ends, check if we can balance by expanding down
|
277
|
+
elsif (kw_count - end_count) == 1 && next_down
|
278
|
+
return self unless next_down.is_end?
|
279
|
+
return self unless next_down.indent >= @orig_indent
|
280
|
+
|
281
|
+
@after_index = next_down.index
|
282
|
+
end
|
283
|
+
self
|
284
|
+
end
|
285
|
+
|
286
|
+
# Finds code lines at the same or greater indentation and adds them
|
287
|
+
# to the block
|
288
|
+
def scan_neighbors_not_empty
|
170
289
|
scan_while { |line| line.not_empty? && line.indent >= @orig_indent }
|
171
290
|
end
|
172
291
|
|
292
|
+
# Returns the next line to be scanned above the current block.
|
293
|
+
# Returns `nil` if at the top of the document already
|
173
294
|
def next_up
|
174
295
|
@code_lines[before_index.pred]
|
175
296
|
end
|
176
297
|
|
298
|
+
# Returns the next line to be scanned below the current block.
|
299
|
+
# Returns `nil` if at the bottom of the document already
|
177
300
|
def next_down
|
178
301
|
@code_lines[after_index.next]
|
179
302
|
end
|
180
303
|
|
304
|
+
# Scan blocks based on indentation of next line above/below block
|
305
|
+
#
|
306
|
+
# Determines indentaion of the next line above/below the current block.
|
307
|
+
#
|
308
|
+
# Normally this is called when a block has expanded to capture all "neighbors"
|
309
|
+
# at the same (or greater) indentation and needs to expand out. For example
|
310
|
+
# the `def/end` lines surrounding a method.
|
181
311
|
def scan_adjacent_indent
|
182
312
|
before_after_indent = []
|
183
313
|
before_after_indent << (next_up&.indent || 0)
|
@@ -189,6 +319,16 @@ module SyntaxSuggest
|
|
189
319
|
self
|
190
320
|
end
|
191
321
|
|
322
|
+
# TODO: Doc or delete
|
323
|
+
#
|
324
|
+
# I don't remember why this is needed, but it's called in code_context.
|
325
|
+
# It's related to the implementation of `capture_neighbor_context` somehow
|
326
|
+
# and that display improvement is only triggered when there's one visible line
|
327
|
+
#
|
328
|
+
# I think the primary purpose is to not include the current line in the
|
329
|
+
# logic evaluation of `capture_neighbor_context`. If that's true, then
|
330
|
+
# we should fix that method to handle this logic instead of only using
|
331
|
+
# it in one place and together.
|
192
332
|
def start_at_next_line
|
193
333
|
before_index
|
194
334
|
after_index
|
@@ -197,26 +337,39 @@ module SyntaxSuggest
|
|
197
337
|
self
|
198
338
|
end
|
199
339
|
|
340
|
+
# Return the currently matched lines as a `CodeBlock`
|
341
|
+
#
|
342
|
+
# When a `CodeBlock` is created it will gather metadata about
|
343
|
+
# itself, so this is not a free conversion. Avoid allocating
|
344
|
+
# more CodeBlock's than needed
|
200
345
|
def code_block
|
201
346
|
CodeBlock.new(lines: lines)
|
202
347
|
end
|
203
348
|
|
349
|
+
# Returns the lines matched by the current scan as an
|
350
|
+
# array of CodeLines
|
204
351
|
def lines
|
205
352
|
@code_lines[before_index..after_index]
|
206
353
|
end
|
207
354
|
|
355
|
+
# Gives the index of the first line currently scanned
|
208
356
|
def before_index
|
209
357
|
@before_index ||= @orig_before_index
|
210
358
|
end
|
211
359
|
|
360
|
+
# Gives the index of the last line currently scanned
|
212
361
|
def after_index
|
213
362
|
@after_index ||= @orig_after_index
|
214
363
|
end
|
215
364
|
|
365
|
+
# Returns an array of all the CodeLines that exist before
|
366
|
+
# the currently scanned block
|
216
367
|
private def before_lines
|
217
368
|
@code_lines[0...before_index] || []
|
218
369
|
end
|
219
370
|
|
371
|
+
# Returns an array of all the CodeLines that exist after
|
372
|
+
# the currently scanned block
|
220
373
|
private def after_lines
|
221
374
|
@code_lines[after_index.next..-1] || []
|
222
375
|
end
|
@@ -35,30 +35,115 @@ module SyntaxSuggest
|
|
35
35
|
@code_lines = code_lines
|
36
36
|
end
|
37
37
|
|
38
|
+
# Main interface. Expand current indentation, before
|
39
|
+
# expanding to a lower indentation
|
38
40
|
def call(block)
|
39
41
|
if (next_block = expand_neighbors(block))
|
40
|
-
|
42
|
+
next_block
|
43
|
+
else
|
44
|
+
expand_indent(block)
|
41
45
|
end
|
42
|
-
|
43
|
-
expand_indent(block)
|
44
46
|
end
|
45
47
|
|
48
|
+
# Expands code to the next lowest indentation
|
49
|
+
#
|
50
|
+
# For example:
|
51
|
+
#
|
52
|
+
# 1 def dog
|
53
|
+
# 2 print "dog"
|
54
|
+
# 3 end
|
55
|
+
#
|
56
|
+
# If a block starts on line 2 then it has captured all it's "neighbors" (code at
|
57
|
+
# the same indentation or higher). To continue expanding, this block must capture
|
58
|
+
# lines one and three which are at a different indentation level.
|
59
|
+
#
|
60
|
+
# This method allows fully expanded blocks to decrease their indentation level (so
|
61
|
+
# they can expand to capture more code up and down). It does this conservatively
|
62
|
+
# as there's no undo (currently).
|
46
63
|
def expand_indent(block)
|
47
64
|
AroundBlockScan.new(code_lines: @code_lines, block: block)
|
48
|
-
.
|
65
|
+
.force_add_hidden
|
49
66
|
.stop_after_kw
|
50
67
|
.scan_adjacent_indent
|
51
68
|
.code_block
|
52
69
|
end
|
53
70
|
|
71
|
+
# A neighbor is code that is at or above the current indent line.
|
72
|
+
#
|
73
|
+
# First we build a block with all neighbors. If we can't go further
|
74
|
+
# then we decrease the indentation threshold and expand via indentation
|
75
|
+
# i.e. `expand_indent`
|
76
|
+
#
|
77
|
+
# Handles two general cases.
|
78
|
+
#
|
79
|
+
# ## Case #1: Check code inside of methods/classes/etc.
|
80
|
+
#
|
81
|
+
# It's important to note, that not everything in a given indentation level can be parsed
|
82
|
+
# as valid code even if it's part of valid code. For example:
|
83
|
+
#
|
84
|
+
# 1 hash = {
|
85
|
+
# 2 name: "richard",
|
86
|
+
# 3 dog: "cinco",
|
87
|
+
# 4 }
|
88
|
+
#
|
89
|
+
# In this case lines 2 and 3 will be neighbors, but they're invalid until `expand_indent`
|
90
|
+
# is called on them.
|
91
|
+
#
|
92
|
+
# When we are adding code within a method or class (at the same indentation level),
|
93
|
+
# use the empty lines to denote the programmer intended logical chunks.
|
94
|
+
# Stop and check each one. For example:
|
95
|
+
#
|
96
|
+
# 1 def dog
|
97
|
+
# 2 print "dog"
|
98
|
+
# 3
|
99
|
+
# 4 hash = {
|
100
|
+
# 5 end
|
101
|
+
#
|
102
|
+
# If we did not stop parsing at empty newlines then the block might mistakenly grab all
|
103
|
+
# the contents (lines 2, 3, and 4) and report them as being problems, instead of only
|
104
|
+
# line 4.
|
105
|
+
#
|
106
|
+
# ## Case #2: Expand/grab other logical blocks
|
107
|
+
#
|
108
|
+
# Once the search algorithm has converted all lines into blocks at a given indentation
|
109
|
+
# it will then `expand_indent`. Once the blocks that generates are expanded as neighbors
|
110
|
+
# we then begin seeing neighbors being other logical blocks i.e. a block's neighbors
|
111
|
+
# may be another method or class (something with keywords/ends).
|
112
|
+
#
|
113
|
+
# For example:
|
114
|
+
#
|
115
|
+
# 1 def bark
|
116
|
+
# 2
|
117
|
+
# 3 end
|
118
|
+
# 4
|
119
|
+
# 5 def sit
|
120
|
+
# 6 end
|
121
|
+
#
|
122
|
+
# In this case if lines 4, 5, and 6 are in a block when it tries to expand neighbors
|
123
|
+
# it will expand up. If it stops after line 2 or 3 it may cause problems since there's a
|
124
|
+
# valid kw/end pair, but the block will be checked without it.
|
125
|
+
#
|
126
|
+
# We try to resolve this edge case with `lookahead_balance_one_line` below.
|
54
127
|
def expand_neighbors(block)
|
55
|
-
|
56
|
-
.
|
128
|
+
neighbors = AroundBlockScan.new(code_lines: @code_lines, block: block)
|
129
|
+
.force_add_hidden
|
57
130
|
.stop_after_kw
|
58
|
-
.
|
59
|
-
|
131
|
+
.scan_neighbors_not_empty
|
132
|
+
|
133
|
+
# Slurp up empties
|
134
|
+
with_empties = neighbors
|
135
|
+
.scan_while { |line| line.empty? }
|
136
|
+
|
137
|
+
# If next line is kw and it will balance us, take it
|
138
|
+
expanded_lines = with_empties
|
139
|
+
.lookahead_balance_one_line
|
60
140
|
.lines
|
61
141
|
|
142
|
+
# Don't allocate a block if it won't be used
|
143
|
+
#
|
144
|
+
# If nothing was taken, return nil to indicate that status
|
145
|
+
# used in `def call` to determine if
|
146
|
+
# we need to expand up/out (`expand_indent`)
|
62
147
|
if block.lines == expanded_lines
|
63
148
|
nil
|
64
149
|
else
|
@@ -110,7 +110,7 @@ module SyntaxSuggest
|
|
110
110
|
@document.join
|
111
111
|
end
|
112
112
|
|
113
|
-
# Remove comments
|
113
|
+
# Remove comments
|
114
114
|
#
|
115
115
|
# replace with empty newlines
|
116
116
|
#
|
@@ -155,8 +155,10 @@ module SyntaxSuggest
|
|
155
155
|
# ).to eq(2)
|
156
156
|
#
|
157
157
|
def clean_sweep(source:)
|
158
|
+
# Match comments, but not HEREDOC strings with #{variable} interpolation
|
159
|
+
# https://rubular.com/r/HPwtW9OYxKUHXQ
|
158
160
|
source.lines.map do |line|
|
159
|
-
if line.match?(/^\s
|
161
|
+
if line.match?(/^\s*#([^{].*|)$/)
|
160
162
|
$/
|
161
163
|
else
|
162
164
|
line
|
@@ -48,12 +48,10 @@ module SyntaxSuggest
|
|
48
48
|
strip_line = line.dup
|
49
49
|
strip_line.lstrip!
|
50
50
|
|
51
|
-
if strip_line.empty?
|
52
|
-
|
53
|
-
@indent = 0
|
51
|
+
@indent = if (@empty = strip_line.empty?)
|
52
|
+
line.length - 1 # Newline removed from strip_line is not "whitespace"
|
54
53
|
else
|
55
|
-
|
56
|
-
@indent = line.length - strip_line.length
|
54
|
+
line.length - strip_line.length
|
57
55
|
end
|
58
56
|
|
59
57
|
set_kw_end
|
@@ -3,6 +3,10 @@
|
|
3
3
|
# Ruby 3.2+ has a cleaner way to hook into Ruby that doesn't use `require`
|
4
4
|
if SyntaxError.method_defined?(:detailed_message)
|
5
5
|
module SyntaxSuggest
|
6
|
+
# Mini String IO [Private]
|
7
|
+
#
|
8
|
+
# Acts like a StringIO with reduced API, but without having to require that
|
9
|
+
# class.
|
6
10
|
class MiniStringIO
|
7
11
|
def initialize(isatty: $stderr.isatty)
|
8
12
|
@string = +""
|
@@ -16,52 +20,59 @@ if SyntaxError.method_defined?(:detailed_message)
|
|
16
20
|
|
17
21
|
attr_reader :string
|
18
22
|
end
|
19
|
-
end
|
20
|
-
|
21
|
-
SyntaxError.prepend Module.new {
|
22
|
-
def detailed_message(highlight: true, syntax_suggest: true, **kwargs)
|
23
|
-
return super unless syntax_suggest
|
24
|
-
|
25
|
-
require "syntax_suggest/api" unless defined?(SyntaxSuggest::DEFAULT_VALUE)
|
26
|
-
|
27
|
-
message = super
|
28
|
-
file = if highlight
|
29
|
-
SyntaxSuggest::PathnameFromMessage.new(super(highlight: false, **kwargs)).call.name
|
30
|
-
else
|
31
|
-
SyntaxSuggest::PathnameFromMessage.new(message).call.name
|
32
|
-
end
|
33
|
-
|
34
|
-
io = SyntaxSuggest::MiniStringIO.new
|
35
23
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
24
|
+
# SyntaxSuggest.record_dir [Private]
|
25
|
+
#
|
26
|
+
# Used to monkeypatch SyntaxError via Module.prepend
|
27
|
+
def self.module_for_detailed_message
|
28
|
+
Module.new {
|
29
|
+
def detailed_message(highlight: true, syntax_suggest: true, **kwargs)
|
30
|
+
return super unless syntax_suggest
|
31
|
+
|
32
|
+
require "syntax_suggest/api" unless defined?(SyntaxSuggest::DEFAULT_VALUE)
|
33
|
+
|
34
|
+
message = super
|
35
|
+
|
36
|
+
if path
|
37
|
+
file = Pathname.new(path)
|
38
|
+
io = SyntaxSuggest::MiniStringIO.new
|
39
|
+
|
40
|
+
SyntaxSuggest.call(
|
41
|
+
io: io,
|
42
|
+
source: file.read,
|
43
|
+
filename: file,
|
44
|
+
terminal: highlight
|
45
|
+
)
|
46
|
+
annotation = io.string
|
47
|
+
|
48
|
+
annotation + message
|
49
|
+
else
|
50
|
+
message
|
51
|
+
end
|
52
|
+
rescue => e
|
53
|
+
if ENV["SYNTAX_SUGGEST_DEBUG"]
|
54
|
+
$stderr.warn(e.message)
|
55
|
+
$stderr.warn(e.backtrace)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Ignore internal errors
|
59
|
+
message
|
60
|
+
end
|
61
|
+
}
|
57
62
|
end
|
58
|
-
|
63
|
+
end
|
64
|
+
|
65
|
+
SyntaxError.prepend(SyntaxSuggest.module_for_detailed_message)
|
59
66
|
else
|
60
67
|
autoload :Pathname, "pathname"
|
61
68
|
|
69
|
+
#--
|
62
70
|
# Monkey patch kernel to ensure that all `require` calls call the same
|
63
71
|
# method
|
72
|
+
#++
|
64
73
|
module Kernel
|
74
|
+
# :stopdoc:
|
75
|
+
|
65
76
|
module_function
|
66
77
|
|
67
78
|
alias_method :syntax_suggest_original_require, :require
|
@@ -36,8 +36,8 @@ module SyntaxSuggest
|
|
36
36
|
# Builds blocks from bottom up
|
37
37
|
def each_neighbor_block(target_line)
|
38
38
|
scan = AroundBlockScan.new(code_lines: code_lines, block: CodeBlock.new(lines: target_line))
|
39
|
-
.
|
40
|
-
.
|
39
|
+
.force_add_empty
|
40
|
+
.force_add_hidden
|
41
41
|
.scan_while { |line| line.indent >= target_line.indent }
|
42
42
|
|
43
43
|
neighbors = scan.code_block.lines
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syntax_suggest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schneems
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: When you get an "unexpected end" in your syntax this gem helps you find
|
14
14
|
it
|
@@ -19,6 +19,7 @@ executables:
|
|
19
19
|
extensions: []
|
20
20
|
extra_rdoc_files: []
|
21
21
|
files:
|
22
|
+
- ".github/dependabot.yml"
|
22
23
|
- ".github/workflows/check_changelog.yml"
|
23
24
|
- ".github/workflows/ci.yml"
|
24
25
|
- ".gitignore"
|
@@ -32,6 +33,8 @@ files:
|
|
32
33
|
- README.md
|
33
34
|
- Rakefile
|
34
35
|
- bin/console
|
36
|
+
- bin/rake
|
37
|
+
- bin/rspec
|
35
38
|
- bin/setup
|
36
39
|
- exe/syntax_suggest
|
37
40
|
- lib/syntax_suggest.rb
|
@@ -81,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
84
|
- !ruby/object:Gem::Version
|
82
85
|
version: '0'
|
83
86
|
requirements: []
|
84
|
-
rubygems_version: 3.
|
87
|
+
rubygems_version: 3.4.6
|
85
88
|
signing_key:
|
86
89
|
specification_version: 4
|
87
90
|
summary: Find syntax errors in your source in a snap
|