syntax_search 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SyntaxErrorSearch
4
- # Represents a single line of code of a given source file
5
- #
6
- # This object contains metadata about the line such as
7
- # amount of indentation. An if it is empty or not.
8
- #
9
- # While a given search for syntax errors is being performed
10
- # state about the search can be stored in individual lines such
11
- # as :valid or :invalid.
12
- #
13
- # Visibility of lines can be toggled on and off.
14
- #
15
- # Example:
16
- #
17
- # line = CodeLine.new(line: "def foo\n", index: 0)
18
- # line.line_number => 1
19
- # line.empty? # => false
20
- # line.visible? # => true
21
- # line.mark_invisible
22
- # line.visible? # => false
23
- #
24
- # A CodeBlock is made of multiple CodeLines
25
- #
26
- # Marking a line as invisible indicates that it should not be used
27
- # for syntax checks. It's essentially the same as commenting it out
28
- #
29
- # Marking a line as invisible also lets the overall program know
30
- # that it should not check that area for syntax errors.
31
- class CodeLine
32
- attr_reader :line, :index, :indent
33
-
34
- def initialize(line: , index:)
35
- @original_line = line.freeze
36
- @line = @original_line
37
- @empty = line.strip.empty?
38
- @index = index
39
- @indent = SpaceCount.indent(line)
40
- @status = nil # valid, invalid, unknown
41
- @invalid = false
42
- end
43
-
44
- def mark_invalid
45
- @invalid = true
46
- self
47
- end
48
-
49
- def marked_invalid?
50
- @invalid
51
- end
52
-
53
- def mark_invisible
54
- @line = ""
55
- self
56
- end
57
-
58
- def mark_visible
59
- @line = @original_line
60
- self
61
- end
62
-
63
- def visible?
64
- !line.empty?
65
- end
66
-
67
- def hidden?
68
- !visible?
69
- end
70
-
71
- def line_number
72
- index + 1
73
- end
74
-
75
- def not_empty?
76
- !empty?
77
- end
78
-
79
- def empty?
80
- @empty
81
- end
82
-
83
- def to_s
84
- self.line
85
- end
86
- end
87
- end
@@ -1,114 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SyntaxErrorSearch
4
- # Searches code for a syntax error
5
- #
6
- # The bulk of the heavy lifting is done by the CodeFrontier
7
- #
8
- # The flow looks like this:
9
- #
10
- # ## Syntax error detection
11
- #
12
- # When the frontier holds the syntax error, we can stop searching
13
- #
14
- #
15
- # search = CodeSearch.new(<<~EOM)
16
- # def dog
17
- # def lol
18
- # end
19
- # EOM
20
- #
21
- # search.call
22
- #
23
- # search.invalid_blocks.map(&:to_s) # =>
24
- # # => ["def lol\n"]
25
- #
26
- #
27
- class CodeSearch
28
- private; attr_reader :frontier; public
29
- public; attr_reader :invalid_blocks, :record_dir, :code_lines
30
-
31
- def initialize(string, record_dir: ENV["SYNTAX_SEARCH_RECORD_DIR"])
32
- if record_dir
33
- @time = Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N')
34
- @record_dir = Pathname(record_dir).join(@time).tap {|p| p.mkpath }
35
- @write_count = 0
36
- end
37
- @code_lines = string.lines.map.with_index do |line, i|
38
- CodeLine.new(line: line, index: i)
39
- end
40
- @frontier = CodeFrontier.new(code_lines: @code_lines)
41
- @invalid_blocks = []
42
- @name_tick = Hash.new {|hash, k| hash[k] = 0 }
43
- @tick = 0
44
- @scan = IndentScan.new(code_lines: @code_lines)
45
- end
46
-
47
- def record(block:, name: "record")
48
- return if !@record_dir
49
- @name_tick[name] += 1
50
- filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}.txt"
51
- @record_dir.join(filename).open(mode: "a") do |f|
52
- display = DisplayInvalidBlocks.new(
53
- blocks: block,
54
- terminal: false
55
- )
56
- f.write(display.indent display.code_with_lines)
57
- end
58
- end
59
-
60
- def push_if_invalid(block, name: )
61
- frontier.register(block)
62
- record(block: block, name: name)
63
-
64
- if block.valid?
65
- block.lines.each(&:mark_invisible)
66
- frontier << block
67
- else
68
- frontier << block
69
- end
70
- end
71
-
72
- def add_invalid_blocks
73
- max_indent = frontier.next_indent_line&.indent
74
-
75
- while (line = frontier.next_indent_line) && (line.indent == max_indent)
76
- neighbors = @scan.neighbors_from_top(frontier.next_indent_line)
77
-
78
- @scan.each_neighbor_block(frontier.next_indent_line) do |block|
79
- record(block: block, name: "add")
80
- if block.valid?
81
- block.lines.each(&:mark_invisible)
82
- end
83
- end
84
-
85
- block = CodeBlock.new(lines: neighbors, code_lines: @code_lines)
86
- push_if_invalid(block, name: "add")
87
- end
88
- end
89
-
90
- def expand_invalid_block
91
- block = frontier.pop
92
- return unless block
93
-
94
- block.expand_until_next_boundry
95
- push_if_invalid(block, name: "expand")
96
- end
97
-
98
- def call
99
- until frontier.holds_all_syntax_errors?
100
- @tick += 1
101
-
102
- if frontier.expand?
103
- expand_invalid_block
104
- else
105
- add_invalid_blocks
106
- end
107
- end
108
-
109
- @invalid_blocks.concat(frontier.detect_invalid_blocks )
110
- @invalid_blocks.sort_by! {|block| block.starts_at }
111
- self
112
- end
113
- end
114
- end
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SyntaxErrorSearch
4
- # Used for formatting invalid blocks
5
- class DisplayInvalidBlocks
6
- attr_reader :filename
7
-
8
- def initialize(blocks:, io: $stderr, filename: nil, terminal: false, invalid_type: :unmatched_end)
9
- @terminal = terminal
10
- @filename = filename
11
- @io = io
12
-
13
- @blocks = Array(blocks)
14
- @lines = @blocks.map(&:lines).flatten
15
- @code_lines = @blocks.first&.code_lines || []
16
- @digit_count = @code_lines.last&.line_number.to_s.length
17
-
18
- @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true }
19
- @invalid_type = invalid_type
20
- end
21
-
22
- def call
23
- if @blocks.any?
24
- found_invalid_blocks
25
- else
26
- @io.puts "Syntax OK"
27
- end
28
- self
29
- end
30
-
31
- private def no_invalid_blocks
32
- @io.puts <<~EOM
33
- EOM
34
- end
35
-
36
- private def found_invalid_blocks
37
- case @invalid_type
38
- when :missing_end
39
- @io.puts <<~EOM
40
-
41
- SyntaxSearch: Missing `end` detected
42
-
43
- This code has a missing `end`. Ensure that all
44
- syntax keywords (`def`, `do`, etc.) have a matching `end`.
45
-
46
- EOM
47
- when :unmatched_end
48
- @io.puts <<~EOM
49
-
50
- SyntaxSearch: Unmatched `end` detected
51
-
52
- This code has an unmatched `end`. Ensure that all `end` lines
53
- in your code have a matching syntax keyword (`def`, `do`, etc.)
54
- and that you don't have any extra `end` lines.
55
-
56
- EOM
57
- end
58
-
59
- @io.puts("file: #{filename}") if filename
60
- @io.puts <<~EOM
61
- simplified:
62
-
63
- #{indent(code_block)}
64
- EOM
65
- end
66
-
67
- def indent(string, with: " ")
68
- string.each_line.map {|l| with + l }.join
69
- end
70
-
71
- def code_block
72
- string = String.new("")
73
- string << code_with_lines
74
- string
75
- end
76
-
77
- def terminal_end
78
- "\e[0m"
79
- end
80
-
81
- def terminal_highlight
82
- "\e[1;3m" # Bold, italics
83
- end
84
-
85
- def code_with_lines
86
- @code_lines.map do |line|
87
- next if line.hidden?
88
-
89
- string = String.new("")
90
- if @invalid_line_hash[line]
91
- string << "❯ "
92
- else
93
- string << " "
94
- end
95
-
96
- number = line.line_number.to_s.rjust(@digit_count)
97
- string << number.to_s
98
- if line.empty?
99
- string << line.to_s
100
- else
101
- string << " "
102
- string << terminal_highlight if @terminal && @invalid_line_hash[line] # Bold, italics
103
- string << line.to_s
104
- string << terminal_end if @terminal
105
- end
106
- string
107
- end.join
108
- end
109
- end
110
- end
@@ -1,7 +0,0 @@
1
- require_relative "../syntax_search"
2
-
3
- require_relative "auto.rb"
4
-
5
- SyntaxErrorSearch.send(:remove_const, :SEARCH_SOURCE_ON_ERROR_DEFAULT)
6
- SyntaxErrorSearch::SEARCH_SOURCE_ON_ERROR_DEFAULT = false
7
-
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SyntaxErrorSearch
4
- VERSION = "0.1.2"
5
- end