syntax_search 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64d391c1ee60a9784b3b53ec9c404739b06dd390d263793795110cdc555bdfb0
4
- data.tar.gz: 70cbe011308ad0c94c88240e5aadf4e7e024b0f83ff65377b99fe82550779ebc
3
+ metadata.gz: 66acadf6481512affd39e4a1fb691ed41a6be537a8a63801f71316863d30494d
4
+ data.tar.gz: 1bcb85706516cbe5c3f53a1ed48bf5215479672952fc62d2e3221c583e468b22
5
5
  SHA512:
6
- metadata.gz: 119be89600a50534968502162df65d972a4fe1ecd93a70378e843cbe22effbe0d3f4a7fb12761740efa95152d81bf3a30abc79401cfbff4ba8ad89e9b9390a47
7
- data.tar.gz: aa93927ba70886754499d263087c89a546d2791b8de3911175e365d7aa111b1d8d659dd3ae015e8297b75b62b9c4e4958c8e9f6c08ac9a4d444f18ccdc7ad8fb
6
+ metadata.gz: 4e60777bd284d78436db8608f34d11b4b5aa8456bfe0c9507ddf3a0e3b97b902a2d00d1537433ed914c637a0729ccb253fc9047fb6db9e3b18a8cc8d0294ae06
7
+ data.tar.gz: 2fb1b35e70507f4b358be4b767cbf266c2d47c55efba1b77d7ca5f260cac4e77d519fc7439f55c78c5294c0c87240d15b4cab3721c8cfd580b518f5051b7cd0c
@@ -0,0 +1,13 @@
1
+ name: Check Changelog
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, reopened, edited, synchronize]
6
+ jobs:
7
+ build:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v1
11
+ - name: Check that CHANGELOG is touched
12
+ run: |
13
+ cat $GITHUB_EVENT_PATH | jq .pull_request.title | grep -i '\[\(\(changelog skip\)\|\(ci skip\)\)\]' || git diff remotes/origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md
@@ -1,8 +1,31 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 0.2.0
4
+
5
+ - Simplify large file output so minimal context around the invalid section is shown (https://github.com/zombocom/syntax_search/pull/26)
6
+ - Block expansion is now lexically aware of keywords (def/do/end etc.) (https://github.com/zombocom/syntax_search/pull/24)
7
+ - Fix bug where not all of a source is lexed which is used in heredoc detection/removal (https://github.com/zombocom/syntax_search/pull/23)
8
+
9
+ ## 0.1.5
10
+
11
+ - Strip out heredocs in documents first (https://github.com/zombocom/syntax_search/pull/19)
12
+
13
+ ## 0.1.4
14
+
15
+ - Parser gem replaced with Ripper (https://github.com/zombocom/syntax_search/pull/17)
16
+
17
+ ## 0.1.3
18
+
19
+ - Internal refactor (https://github.com/zombocom/syntax_search/pull/13)
20
+
21
+ ## 0.1.2
22
+
23
+ - Codeblocks in output are now indented with 4 spaces and "code fences" are removed (https://github.com/zombocom/syntax_search/pull/11)
24
+ - "Unmatched end" and "missing end" not generate different error text instructions (https://github.com/zombocom/syntax_search/pull/10)
25
+
3
26
  ## 0.1.1
4
27
 
5
- - Fix error message detection to fire on more rubies ()
28
+ - Fire search on both unexpected end-of-input and unexected end (https://github.com/zombocom/syntax_search/pull/8)
6
29
 
7
30
  ## 0.1.0
8
31
 
data/Gemfile CHANGED
@@ -7,3 +7,4 @@ gemspec
7
7
 
8
8
  gem "rake", "~> 12.0"
9
9
  gem "rspec", "~> 3.0"
10
+ gem "stackprof"
@@ -1,16 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_search (0.1.1)
5
- parser
4
+ syntax_search (0.2.0)
6
5
 
7
6
  GEM
8
7
  remote: https://rubygems.org/
9
8
  specs:
10
- ast (2.4.1)
11
9
  diff-lcs (1.4.4)
12
- parser (2.7.2.0)
13
- ast (~> 2.4.1)
14
10
  rake (12.3.3)
15
11
  rspec (3.10.0)
16
12
  rspec-core (~> 3.10.0)
@@ -25,6 +21,7 @@ GEM
25
21
  diff-lcs (>= 1.2.0, < 2.0)
26
22
  rspec-support (~> 3.10.0)
27
23
  rspec-support (3.10.0)
24
+ stackprof (0.2.16)
28
25
 
29
26
  PLATFORMS
30
27
  ruby
@@ -32,6 +29,7 @@ PLATFORMS
32
29
  DEPENDENCIES
33
30
  rake (~> 12.0)
34
31
  rspec (~> 3.0)
32
+ stackprof
35
33
  syntax_search!
36
34
 
37
35
  BUNDLED WITH
data/README.md CHANGED
@@ -10,30 +10,29 @@ What happened? Likely you forgot a `def`, `do`, or maybe you deleted some code a
10
10
 
11
11
  What if I told you, that there was a library that helped find your missing `def`s and missing `do`s. What if instead of searching through hundreds of lines of source for the cause of your syntax error, there was a way to highlight just code in the file that contained syntax errors.
12
12
 
13
- $ syntax_search <path/to/file.rb>
13
+ $ syntax_search path/to/file.rb
14
14
 
15
- SyntaxErrorSearch: A syntax error was detected
15
+ SyntaxSearch: Unmatched `end` detected
16
16
 
17
- This code has an unmatched `end` this is caused by either
18
- missing a syntax keyword (`def`, `do`, etc.) or inclusion
19
- of an extra `end` line
17
+ This code has an unmatched `end`. Ensure that all `end` lines
18
+ in your code have a matching syntax keyword (`def`, `do`, etc.)
19
+ and that you don't have any extra `end` lines.
20
20
 
21
21
  file: path/to/file.rb
22
22
  simplified:
23
23
 
24
- ```
25
- 1 require 'animals'
26
- 2
27
- ❯ 10 defdog
28
- 15 end
29
- 16
30
- 20 def cat
31
- 22 end
32
- ```
24
+ 1 require 'zoo'
25
+ 2
26
+ 3 class Animal
27
+ 4
28
+ 5 defdog
29
+ 7 end
30
+ 8
31
+ 12 end
33
32
 
34
33
  How much would you pay for such a library? A million, a billion, a trillion? Well friends, today is your lucky day because you can use this library today for free!
35
34
 
36
- ## Installation
35
+ ## Installation in your codebase
37
36
 
38
37
  To automatically search syntax errors when they happen, add this to your Gemfile:
39
38
 
@@ -51,6 +50,16 @@ If your application is not calling `Bundler.require` then you must manually add
51
50
  require "syntax_search/auto"
52
51
  ```
53
52
 
53
+ If you're using rspec add this to your `.rspec` file:
54
+
55
+ ```
56
+ --require syntax_search/auto
57
+ ```
58
+
59
+ > This is needed because people can execute a single test file via `bundle exec rspec path/to/file_spec.rb` and if that file has a syntax error, it won't load `spec_helper.rb` to trigger any requires.
60
+
61
+ ## Install the CLI
62
+
54
63
  To get the CLI and manually search for syntax errors, install the gem:
55
64
 
56
65
  $ gem install syntax_search
@@ -74,6 +83,10 @@ We know that source code that does not contain a syntax error can be parsed. We
74
83
 
75
84
  Since there can be multiple syntax errors in a document it's not good enough to check individual code blocks, we've got to check multiple at the same time. We will keep creating and adding new blocks to our search until we detect that our "frontier" (which contains all of our blocks) contains the syntax error. After this, we can stop our search and instead focus on filtering to find the smallest subset of blocks that contain the syntax error.
76
85
 
86
+ Here's an example:
87
+
88
+ ![](assets/syntax_search.gif)
89
+
77
90
  ## How is source code broken up into smaller blocks?
78
91
 
79
92
  By definition source code with a syntax error in it cannot be parsed, so we have to guess how to chunk up the file into smaller pieces. Once we've split up the file we can safely rule out or zoom into a specific piece of code to determine the location of the syntax error. This libary uses indentation and empty lines to make guesses about what might be a "block" of code. Once we've got a chunk of code, we can test it.
Binary file
@@ -62,6 +62,7 @@ if file.nil? || file.empty?
62
62
  end
63
63
 
64
64
  file = Pathname(file)
65
+ options[:record_dir] = "tmp" if ENV["DEBUG"]
65
66
 
66
67
  $stderr.puts "Record dir: #{options[:record_dir]}" if options[:record_dir]
67
68
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  require_relative "syntax_search/version"
4
4
 
5
- require 'parser/current'
6
5
  require 'tmpdir'
7
6
  require 'stringio'
8
7
  require 'pathname'
8
+ require 'ripper'
9
9
 
10
10
  module SyntaxErrorSearch
11
11
  class Error < StandardError; end
@@ -23,7 +23,7 @@ module SyntaxErrorSearch
23
23
  self.call(
24
24
  source: Pathname(filename).read,
25
25
  filename: filename,
26
- terminal: true
26
+ terminal: true,
27
27
  )
28
28
  end
29
29
 
@@ -40,6 +40,8 @@ module SyntaxErrorSearch
40
40
  blocks: blocks,
41
41
  filename: filename,
42
42
  terminal: terminal,
43
+ code_lines: search.code_lines,
44
+ invalid_type: invalid_type(source),
43
45
  io: $stderr
44
46
  ).call
45
47
  end
@@ -78,6 +80,13 @@ module SyntaxErrorSearch
78
80
  end
79
81
  end
80
82
 
83
+ def self.invalid?(source)
84
+ source = source.join if source.is_a?(Array)
85
+ source = source.to_s
86
+
87
+ Ripper.new(source).tap(&:parse).error?
88
+ end
89
+
81
90
  # Returns truthy if a given input source is valid syntax
82
91
  #
83
92
  # SyntaxErrorSearch.valid?(<<~EOM) # => true
@@ -113,24 +122,24 @@ module SyntaxErrorSearch
113
122
  # so passing a CodeLine in as an object or as an array
114
123
  # will convert it to it's code representation.
115
124
  def self.valid?(source)
116
- source = source.join if source.is_a?(Array)
117
- source = source.to_s
125
+ !invalid?(source)
126
+ end
118
127
 
119
- # Parser writes to stderr even if you catch the error
120
- stderr = $stderr
121
- $stderr = StringIO.new
122
128
 
123
- Parser::CurrentRuby.parse(source)
124
- true
125
- rescue Parser::SyntaxError
126
- false
127
- ensure
128
- $stderr = stderr if stderr
129
+ def self.invalid_type(source)
130
+ WhoDisSyntaxError.new(source).call.error_symbol
129
131
  end
130
132
  end
131
133
 
132
134
  require_relative "syntax_search/code_line"
133
135
  require_relative "syntax_search/code_block"
134
136
  require_relative "syntax_search/code_frontier"
135
- require_relative "syntax_search/code_search"
136
137
  require_relative "syntax_search/display_invalid_blocks"
138
+ require_relative "syntax_search/around_block_scan"
139
+ require_relative "syntax_search/block_expand"
140
+ require_relative "syntax_search/parse_blocks_from_indent_line"
141
+
142
+ require_relative "syntax_search/code_search"
143
+ require_relative "syntax_search/who_dis_syntax_error"
144
+ require_relative "syntax_search/heredoc_block_parse"
145
+ require_relative "syntax_search/lex_all"
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ module SyntaxErrorSearch
4
+ # This class is useful for exploring contents before and after
5
+ # a block
6
+ #
7
+ # It searches above and below the passed in block to match for
8
+ # whatever criteria you give it:
9
+ #
10
+ # Example:
11
+ #
12
+ # def dog
13
+ # puts "bark"
14
+ # puts "bark"
15
+ # end
16
+ #
17
+ # scan = AroundBlockScan.new(
18
+ # code_lines: code_lines
19
+ # block: CodeBlock.new(lines: code_lines[1])
20
+ # )
21
+ #
22
+ # scan.scan_while { true }
23
+ #
24
+ # puts scan.before_index # => 0
25
+ # puts scan.after_index # => 3
26
+ #
27
+ # Contents can also be filtered using AroundBlockScan#skip
28
+ #
29
+ # To grab the next surrounding indentation use AroundBlockScan#scan_adjacent_indent
30
+ class AroundBlockScan
31
+ def initialize(code_lines: , block:)
32
+ @code_lines = code_lines
33
+ @orig_before_index = block.lines.first.index
34
+ @orig_after_index = block.lines.last.index
35
+ @orig_indent = block.current_indent
36
+ @skip_array = []
37
+ @after_array = []
38
+ @before_array = []
39
+ @stop_after_kw = false
40
+ end
41
+
42
+ def skip(name)
43
+ @skip_array << name
44
+ self
45
+ end
46
+
47
+ def stop_after_kw
48
+ @stop_after_kw = true
49
+ self
50
+ end
51
+
52
+ def scan_while(&block)
53
+ stop_next = false
54
+
55
+ kw_count = 0
56
+ end_count = 0
57
+ @before_index = before_lines.reverse_each.take_while do |line|
58
+ next false if stop_next
59
+ next true if @skip_array.detect {|meth| line.send(meth) }
60
+
61
+ kw_count += 1 if line.is_kw?
62
+ end_count += 1 if line.is_end?
63
+ if @stop_after_kw && kw_count > end_count
64
+ stop_next = true
65
+ end
66
+
67
+ block.call(line)
68
+ end.reverse.first&.index
69
+
70
+ stop_next = false
71
+ kw_count = 0
72
+ end_count = 0
73
+ @after_index = after_lines.take_while do |line|
74
+ next false if stop_next
75
+ next true if @skip_array.detect {|meth| line.send(meth) }
76
+
77
+ kw_count += 1 if line.is_kw?
78
+ end_count += 1 if line.is_end?
79
+ if @stop_after_kw && end_count > kw_count
80
+ stop_next = true
81
+ end
82
+
83
+ block.call(line)
84
+ end.last&.index
85
+ self
86
+ end
87
+
88
+ def capture_neighbor_context
89
+ lines = []
90
+ kw_count = 0
91
+ end_count = 0
92
+ before_lines.reverse.each do |line|
93
+ next if line.empty?
94
+ break if line.indent < @orig_indent
95
+ next if line.indent != @orig_indent
96
+
97
+ kw_count += 1 if line.is_kw?
98
+ end_count += 1 if line.is_end?
99
+ if kw_count != 0 && kw_count == end_count
100
+ lines << line
101
+ break
102
+ end
103
+
104
+ lines << line
105
+ end
106
+
107
+ lines.reverse!
108
+
109
+ kw_count = 0
110
+ end_count = 0
111
+ after_lines.each do |line|
112
+ # puts "line: #{line.number} #{line.original_line}, indent: #{line.indent}, #{line.empty?} #{line.indent == @orig_indent}"
113
+
114
+ next if line.empty?
115
+ break if line.indent < @orig_indent
116
+ next if line.indent != @orig_indent
117
+
118
+ kw_count += 1 if line.is_kw?
119
+ end_count += 1 if line.is_end?
120
+ if kw_count != 0 && kw_count == end_count
121
+ lines << line
122
+ break
123
+ end
124
+
125
+ lines << line
126
+ end
127
+ lines.select! {|line| !line.is_comment? }
128
+
129
+ lines
130
+ end
131
+
132
+ def on_falling_indent
133
+ last_indent = @orig_indent
134
+ before_lines.reverse.each do |line|
135
+ next if line.empty?
136
+ if line.indent < last_indent
137
+ yield line
138
+ last_indent = line.indent
139
+ end
140
+ end
141
+
142
+ last_indent = @orig_indent
143
+ after_lines.each do |line|
144
+ next if line.empty?
145
+ if line.indent < last_indent
146
+ yield line
147
+ last_indent = line.indent
148
+ end
149
+ end
150
+ end
151
+
152
+ def scan_neighbors
153
+ self.scan_while {|line| line.not_empty? && line.indent >= @orig_indent }
154
+ end
155
+
156
+ def scan_adjacent_indent
157
+ before_indent = @code_lines[@orig_before_index.pred]&.indent || 0
158
+ after_indent = @code_lines[@orig_after_index.next]&.indent || 0
159
+
160
+ indent = [before_indent, after_indent].min
161
+ self.scan_while {|line| line.not_empty? && line.indent >= indent }
162
+
163
+ self
164
+ end
165
+
166
+ def start_at_next_line
167
+ before_index; after_index
168
+ @before_index -= 1
169
+ @after_index += 1
170
+ self
171
+ end
172
+
173
+ def code_block
174
+ CodeBlock.new(lines: @code_lines[before_index..after_index])
175
+ end
176
+
177
+ def before_index
178
+ @before_index ||= @orig_before_index
179
+ end
180
+
181
+ def after_index
182
+ @after_index ||= @orig_after_index
183
+ end
184
+
185
+ private def before_lines
186
+ @code_lines[0...@orig_before_index]
187
+ end
188
+
189
+ private def after_lines
190
+ @code_lines[@orig_after_index.next..-1]
191
+ end
192
+ end
193
+ end