dead_end 2.0.2 → 3.0.3

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: ec9e05716b47eb5d96ac063126a59977d98b8cf3e6f09ba0a0065d425a3c15e5
4
- data.tar.gz: c78047c6dd36c1c716e1970e9bec5887bbcaf1fcc0341cbb5c11405043a07105
3
+ metadata.gz: 88cc140d421e1df16009993e7d7438054dad7a6b6fec740496e92c06c0bc2f6d
4
+ data.tar.gz: ba736b1459f6c18aed62b566fa8e48ca6a4dd8379bc2dc2d99594c0daf136fb6
5
5
  SHA512:
6
- metadata.gz: 2eca38b35ef1371cae5fd104fa88dad33d44b17cb6bf29b01b5dea0c650d3d21c0dfbf2b1ac96f0a6f307e7d541cf2a2f7be062d59f1626b16c82f1a2f7d3a3e
7
- data.tar.gz: 1a09171cb208465cf48ff5ac0346240d5bf155aebb886c27b6259742f325bc58a0b73ec9998fcbb1544440e6df7363fa7ab57a04dba9e7887207f30916e8c3a9
6
+ metadata.gz: 3cf585c9f073344d6259478c239fd45b77a5fb7fd1c37c18d3d2a7cf91323b00750cccb18d666935ea20ce2c209137216304429a26aac0bf26fec7aa5440ff14
7
+ data.tar.gz: 8a818ac4001ce7ec01ae36c4c223f47c7686e111226718cf22e8e2ca9f00a0bb3f3ef6409ad703cb698a67dc62035225973f37ea5f527207c26e705acd4c8f33
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 3.0.3
4
+
5
+ - Expand explanations coming from additional Ripper errors (https://github.com/zombocom/dead_end/pull/117)
6
+ - Fix explanation involving shorthand syntax for literals like `%w[]` and `%Q{}` (https://github.com/zombocom/dead_end/pull/116)
7
+
8
+ ## 3.0.2
9
+
10
+ - Fix windows filename detection (https://github.com/zombocom/dead_end/pull/114)
11
+ - Update links on readme and code of conduct (https://github.com/zombocom/dead_end/pull/107)
12
+
13
+ ## 3.0.1
14
+
15
+ - Fix CLI parsing when flags come before filename (https://github.com/zombocom/dead_end/pull/102)
16
+
17
+ ## 3.0.0
18
+
19
+ - [Breaking] CLI now outputs to STDOUT instead of STDERR (https://github.com/zombocom/dead_end/pull/98)
20
+ - [Breaking] Remove previously deprecated `require "dead_end/fyi"` interface (https://github.com/zombocom/dead_end/pull/94)
21
+ - Fix double output bug (https://github.com/zombocom/dead_end/pull/99)
22
+ - Fix bug causing poor results (fix #95, fix #88) (https://github.com/zombocom/dead_end/pull/96)
23
+ - DeadEnd is now fired on EVERY syntax error (https://github.com/zombocom/dead_end/pull/94)
24
+ - Output format changes:
25
+ - Parse errors emitted per-block rather than for the whole document (https://github.com/zombocom/dead_end/pull/94)
26
+ - The "banner" is now based on lexical analysis rather than parser regex (fix #68, fix #87) (https://github.com/zombocom/dead_end/pull/96)
27
+
3
28
  ## 2.0.2
4
29
 
5
30
  - Don't print terminal color codes when output is not tty (https://github.com/zombocom/dead_end/pull/91)
data/CODE_OF_CONDUCT.md CHANGED
@@ -68,7 +68,7 @@ members of the project's leadership.
68
68
  ## Attribution
69
69
 
70
70
  This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [https://contributor-covenant.org/version/1/4][version]
71
+ available at [https://contributor-covenant.org/version/1/4/code-of-conduct/][version]
72
72
 
73
73
  [homepage]: https://contributor-covenant.org
74
- [version]: https://contributor-covenant.org/version/1/4/
74
+ [version]: https://contributor-covenant.org/version/1/4/code-of-conduct/
data/Gemfile CHANGED
@@ -9,3 +9,4 @@ gem "rake", "~> 12.0"
9
9
  gem "rspec", "~> 3.0"
10
10
  gem "stackprof"
11
11
  gem "standard"
12
+ gem "ruby-prof"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dead_end (2.0.2)
4
+ dead_end (3.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -42,6 +42,7 @@ GEM
42
42
  rubocop-performance (1.11.5)
43
43
  rubocop (>= 1.7.0, < 2.0)
44
44
  rubocop-ast (>= 0.4.0)
45
+ ruby-prof (1.4.3)
45
46
  ruby-progressbar (1.11.0)
46
47
  stackprof (0.2.16)
47
48
  standard (1.3.0)
@@ -56,8 +57,9 @@ DEPENDENCIES
56
57
  dead_end!
57
58
  rake (~> 12.0)
58
59
  rspec (~> 3.0)
60
+ ruby-prof
59
61
  stackprof
60
62
  standard
61
63
 
62
64
  BUNDLED WITH
63
- 2.2.29
65
+ 2.2.30
data/README.md CHANGED
@@ -2,19 +2,14 @@
2
2
 
3
3
  An error in your code forces you to stop. DeadEnd helps you find those errors to get you back on your way faster.
4
4
 
5
- DeadEnd: Unmatched `end` detected
6
-
7
- This code has an unmatched `end`. Ensure that all `end` lines
8
- in your code have a matching syntax keyword (`def`, `do`, etc.)
9
- and that you don't have any extra `end` lines.
10
-
11
- file: path/to/dog.rb
12
- simplified:
5
+ ```
6
+ Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?
13
7
 
14
- 3 class Dog
15
- 5 defbark
16
- 7 end
17
- 12 end
8
+ 1 class Dog
9
+ 2 defbark
10
+ 4 end
11
+ 5 end
12
+ ```
18
13
 
19
14
  ## Installation in your codebase
20
15
 
@@ -50,36 +45,108 @@ To get the CLI and manually search for syntax errors (but not automatically anno
50
45
 
51
46
  This gives you the CLI command `$ dead_end` for more info run `$ dead_end --help`.
52
47
 
48
+ ## Editor integration
49
+
50
+ An extension is available for VSCode:
51
+
52
+ - Extension: https://marketplace.visualstudio.com/items?itemName=Zombocom.dead-end-vscode
53
+ - GitHub: https://github.com/zombocom/dead_end-vscode
54
+
53
55
  ## What syntax errors does it handle?
54
56
 
57
+ Dead end will fire against all syntax errors and can isolate any syntax error. In addition, dead_end attempts to produce human readable descriptions of what needs to be done to resolve the issue. For example:
58
+
55
59
  - Missing `end`:
56
60
 
61
+ <!--
57
62
  ```ruby
58
63
  class Dog
59
64
  def bark
60
65
  puts "bark"
61
-
62
- def woof
63
- puts "woof"
64
- end
65
66
  end
66
- # => scratch.rb:8: syntax error, unexpected end-of-input, expecting `end'
67
67
  ```
68
+ -->
68
69
 
69
- - Unexpected `end`
70
+ ```
71
+ Unmatched keyword, missing `end' ?
70
72
 
73
+ ❯ 1 class Dog
74
+ ❯ 2 def bark
75
+ ❯ 4 end
76
+ ```
77
+
78
+ - Missing keyword
79
+ <!--
71
80
  ```ruby
72
81
  class Dog
73
82
  def speak
74
- @sounds.each |sound| # Note the missing `do` here
83
+ @sounds.each |sound|
75
84
  puts sound
76
85
  end
77
86
  end
78
87
  end
79
- # => scratch.rb:7: syntax error, unexpected `end', expecting end-of-input
80
88
  ```
89
+ -->
81
90
 
82
- As well as unmatched `|` and unmatched `}`. These errors can be time consuming to debug because Ruby often only tells you the last line in the file. The command `ruby -wc path/to/file.rb` can narrow it down a little bit, but this library does a better job.
91
+ ```
92
+ Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?
93
+
94
+ 1 class Dog
95
+ 2 def speak
96
+ ❯ 3 @sounds.each |sound|
97
+ ❯ 5 end
98
+ 6 end
99
+ 7 end
100
+ ```
101
+
102
+ - Missing pair characters (like `{}`, `[]`, `()` , or `|<var>|`)
103
+ <!--
104
+
105
+ ```ruby
106
+ class Dog
107
+ def speak(sound
108
+ puts sound
109
+ end
110
+ end
111
+ ```
112
+ -->
113
+
114
+ ```
115
+ Unmatched `(', missing `)' ?
116
+
117
+ 1 class Dog
118
+ ❯ 2 def speak(sound
119
+ ❯ 4 end
120
+ 5 end
121
+ ```
122
+
123
+ - Any ambiguous or unknown errors will be annotated by the original ripper error output:
124
+
125
+ <!--
126
+ class Dog
127
+ def meals_last_month
128
+ puts 3 *
129
+ end
130
+ end
131
+ -->
132
+
133
+ ```
134
+ syntax error, unexpected end-of-input
135
+
136
+ 1 class Dog
137
+ 2 def meals_last_month
138
+ ❯ 3 puts 3 *
139
+ 4 end
140
+ 5 end
141
+ ```
142
+
143
+ ## How is it better than `ruby -wc`?
144
+
145
+ Ruby allows you to syntax check a file with warnings using `ruby -wc`. This emits a parser error instead of a human focused error. Ruby's parse errors attempt to narrow down the location and can tell you if there is a glaring indentation error involving `end`.
146
+
147
+ The `dead_end` algorithm doesn't just guess at the location of syntax errors, it re-parses the document to prove that it captured them.
148
+
149
+ This library focuses on the human side of syntax errors. It cares less about why the document could not be parsed (computer problem) and more on what the programmer needs (human problem) to fix the problem.
83
150
 
84
151
  ## Sounds cool, but why isn't this baked into Ruby directly?
85
152
 
@@ -105,9 +172,37 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
105
172
 
106
173
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec 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).
107
174
 
175
+ ### How to debug changes to output display
176
+
177
+ You can see changes to output against a variety of invalid code by running specs and using the `DEBUG_DISPLAY=1` environment variable. For example:
178
+
179
+ ```
180
+ $ DEBUG_DISPLAY=1 bundle exec rspec spec/ --format=failures
181
+ ```
182
+
183
+ ### Run profiler
184
+
185
+ You can output profiler data to the `tmp` directory by running:
186
+
187
+ ```
188
+ $ DEBUG_PERF=1 bundle exec rspec spec/integration/dead_end_spec.rb
189
+ ```
190
+
191
+ Some outputs are in text format, some are html, the raw marshaled data is available in `raw.rb.marshal`. See https://ruby-prof.github.io/#reports for more info. One interesting one, is the "kcachegrind" interface. To view this on mac:
192
+
193
+ ```
194
+ $ brew install qcachegrind
195
+ ```
196
+
197
+ Open:
198
+
199
+ ```
200
+ $ qcachegrind tmp/last/profile.callgrind.out.<numbers>
201
+ ```
202
+
108
203
  ## Contributing
109
204
 
110
- Bug reports and pull requests are welcome on GitHub at https://github.com/zombocom/dead_end. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/zombocom/dead_end/blob/master/CODE_OF_CONDUCT.md).
205
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zombocom/dead_end. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/zombocom/dead_end/blob/main/CODE_OF_CONDUCT.md).
111
206
 
112
207
 
113
208
  ## License
@@ -116,4 +211,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
116
211
 
117
212
  ## Code of Conduct
118
213
 
119
- Everyone interacting in the DeadEnd project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/zombocom/dead_end/blob/master/CODE_OF_CONDUCT.md).
214
+ Everyone interacting in the DeadEnd project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/zombocom/dead_end/blob/main/CODE_OF_CONDUCT.md).
data/exe/dead_end CHANGED
@@ -1,81 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "pathname"
4
- require "optparse"
5
3
  require_relative "../lib/dead_end"
6
4
 
7
- options = {}
8
- options[:record_dir] = ENV["DEAD_END_RECORD_DIR"]
9
-
10
- parser = OptionParser.new do |opts|
11
- opts.banner = <<~EOM
12
- Usage: dead_end <file> [options]
13
-
14
- Parses a ruby source file and searches for syntax error(s) such as
15
- unexpected `end', expecting end-of-input.
16
-
17
- Example:
18
-
19
- $ dead_end dog.rb
20
-
21
- # ...
22
-
23
- ❯ 10 defdog
24
- ❯ 15 end
25
- ❯ 16
26
-
27
- Env options:
28
-
29
- DEAD_END_RECORD_DIR=<dir>
30
-
31
- When enabled, records the steps used to search for a syntax error to the
32
- given directory
33
-
34
- Options:
35
- EOM
36
-
37
- opts.version = DeadEnd::VERSION
38
-
39
- opts.on("--help", "Help - displays this message") do |v|
40
- puts opts
41
- exit
42
- end
43
-
44
- opts.on("--record <dir>", "When enabled, records the steps used to search for a syntax error to the given directory") do |v|
45
- options[:record_dir] = v
46
- end
47
-
48
- opts.on("--terminal", "Enable terminal highlighting") do |v|
49
- options[:terminal] = true
50
- end
51
-
52
- opts.on("--no-terminal", "Disable terminal highlighting") do |v|
53
- options[:terminal] = false
54
- end
55
- end
56
- parser.parse!
57
-
58
- file = ARGV[0]
59
-
60
- if file.nil? || file.empty?
61
- # Display help if raw command
62
- parser.parse! %w[--help]
63
- end
64
-
65
- file = Pathname(file)
66
- options[:record_dir] = "tmp" if ENV["DEBUG"]
67
-
68
- warn "Record dir: #{options[:record_dir]}" if options[:record_dir]
69
-
70
- display = DeadEnd.call(
71
- source: file.read,
72
- filename: file.expand_path,
73
- terminal: options.fetch(:terminal, DeadEnd::DEFAULT_VALUE),
74
- record_dir: options[:record_dir]
75
- )
76
-
77
- if display.document_ok?
78
- exit(0)
79
- else
80
- exit(1)
81
- end
5
+ DeadEnd::Cli.new(
6
+ argv: ARGV
7
+ ).call
data/lib/dead_end/auto.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../dead_end/internals"
3
+ require_relative "../dead_end"
4
4
 
5
5
  # Monkey patch kernel to ensure that all `require` calls call the same
6
6
  # method
@@ -33,23 +33,3 @@ module Kernel
33
33
  DeadEnd.handle_error(e)
34
34
  end
35
35
  end
36
-
37
- # I honestly have no idea why this Object delegation is needed
38
- # I keep staring at bootsnap and it doesn't have to do this
39
- # is there a bug in their implementation they haven't caught or
40
- # am I doing something different?
41
- class Object
42
- private
43
-
44
- def load(path, wrap = false)
45
- Kernel.load(path, wrap)
46
- rescue SyntaxError => e
47
- DeadEnd.handle_error(e)
48
- end
49
-
50
- def require(path)
51
- Kernel.require(path)
52
- rescue SyntaxError => e
53
- DeadEnd.handle_error(e)
54
- end
55
- end
@@ -85,17 +85,16 @@ module DeadEnd
85
85
  #
86
86
  class CleanDocument
87
87
  def initialize(source:)
88
- @source = source
89
- @document = CodeLine.from_source(@source)
88
+ lines = clean_sweep(source: source)
89
+ @document = CodeLine.from_source(lines.join, lines: lines)
90
90
  end
91
91
 
92
92
  # Call all of the document "cleaners"
93
93
  # and return self
94
94
  def call
95
- clean_sweep
96
- .join_trailing_slash!
97
- .join_consecutive!
98
- .join_heredoc!
95
+ join_trailing_slash!
96
+ join_consecutive!
97
+ join_heredoc!
99
98
 
100
99
  self
101
100
  end
@@ -122,17 +121,15 @@ module DeadEnd
122
121
  # puts "world"
123
122
  # EOM
124
123
  #
125
- # lines = CleanDocument.new(source: source).clean_sweep.lines
124
+ # lines = CleanDocument.new(source: source).lines
126
125
  # expect(lines[0].to_s).to eq("\n")
127
126
  # expect(lines[1].to_s).to eq("puts "hello")
128
127
  # expect(lines[2].to_s).to eq("\n")
129
128
  # expect(lines[3].to_s).to eq("puts "world")
130
129
  #
131
- # WARNING:
132
- # If you run this after any of the "join" commands, they
133
- # will be un-joined.
130
+ # Important: This must be done before lexing.
134
131
  #
135
- # After this change is made, we re-lex the document because
132
+ # After this change is made, we lex the document because
136
133
  # removing comments can change how the doc is parsed.
137
134
  #
138
135
  # For example:
@@ -142,7 +139,9 @@ module DeadEnd
142
139
  # # comment
143
140
  # where(name: 'schneems')
144
141
  # EOM
145
- # expect(values.count {|v| v.type == :on_ignored_nl}).to eq(1)
142
+ # expect(
143
+ # values.count {|v| v.type == :on_ignored_nl}
144
+ # ).to eq(1)
146
145
  #
147
146
  # After the comment is removed:
148
147
  #
@@ -151,26 +150,18 @@ module DeadEnd
151
150
  #
152
151
  # where(name: 'schneems')
153
152
  # EOM
154
- # expect(values.count {|v| v.type == :on_ignored_nl}).to eq(2)
153
+ # expect(
154
+ # values.count {|v| v.type == :on_ignored_nl}
155
+ # ).to eq(2)
155
156
  #
156
- def clean_sweep
157
- source = @document.map do |code_line|
158
- # Clean trailing whitespace on empty line
159
- if code_line.line.strip.empty?
160
- next CodeLine.new(line: "\n", index: code_line.index, lex: [])
157
+ def clean_sweep(source:)
158
+ source.lines.map do |line|
159
+ if line.match?(/^\s*(#[^{].*)?$/) # https://rubular.com/r/LLE10D8HKMkJvs
160
+ $/
161
+ else
162
+ line
161
163
  end
162
-
163
- # Remove comments
164
- if code_line.lex.detect { |lex| lex.type != :on_sp }&.type == :on_comment
165
- next CodeLine.new(line: "\n", index: code_line.index, lex: [])
166
- end
167
-
168
- code_line
169
- end.join
170
-
171
- @source = source
172
- @document = CodeLine.from_source(source)
173
- self
164
+ end
174
165
  end
175
166
 
176
167
  # Smushes all heredoc lines into one line
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "optparse"
5
+
6
+ module DeadEnd
7
+ # All the logic of the exe/dead_end CLI in one handy spot
8
+ #
9
+ # Cli.new(argv: ["--help"]).call
10
+ # Cli.new(argv: ["<path/to/file>.rb"]).call
11
+ # Cli.new(argv: ["<path/to/file>.rb", "--record=tmp"]).call
12
+ # Cli.new(argv: ["<path/to/file>.rb", "--terminal"]).call
13
+ #
14
+ class Cli
15
+ attr_accessor :options
16
+
17
+ # ARGV is Everything passed to the executable, does not include executable name
18
+ #
19
+ # All other intputs are dependency injection for testing
20
+ def initialize(argv:, exit_obj: Kernel, io: $stdout, env: ENV)
21
+ @options = {}
22
+ @parser = nil
23
+ options[:record_dir] = env["DEAD_END_RECORD_DIR"]
24
+ options[:record_dir] = "tmp" if env["DEBUG"]
25
+ options[:terminal] = DeadEnd::DEFAULT_VALUE
26
+
27
+ @io = io
28
+ @argv = argv
29
+ @exit_obj = exit_obj
30
+ end
31
+
32
+ def call
33
+ if @argv.empty?
34
+ # Display help if raw command
35
+ parser.parse! %w[--help]
36
+ return
37
+ else
38
+ # Mutates @argv
39
+ parse
40
+ return if options[:exit]
41
+ end
42
+
43
+ file_name = @argv.first
44
+ if file_name.nil?
45
+ @io.puts "No file given"
46
+ @exit_obj.exit(1)
47
+ return
48
+ end
49
+
50
+ file = Pathname(file_name)
51
+ if !file.exist?
52
+ @io.puts "file not found: #{file.expand_path} "
53
+ @exit_obj.exit(1)
54
+ return
55
+ end
56
+
57
+ @io.puts "Record dir: #{options[:record_dir]}" if options[:record_dir]
58
+
59
+ display = DeadEnd.call(
60
+ io: @io,
61
+ source: file.read,
62
+ filename: file.expand_path,
63
+ terminal: options.fetch(:terminal, DeadEnd::DEFAULT_VALUE),
64
+ record_dir: options[:record_dir]
65
+ )
66
+
67
+ if display.document_ok?
68
+ @exit_obj.exit(0)
69
+ else
70
+ @exit_obj.exit(1)
71
+ end
72
+ end
73
+
74
+ def parse
75
+ parser.parse!(@argv)
76
+
77
+ self
78
+ end
79
+
80
+ def parser
81
+ @parser ||= OptionParser.new do |opts|
82
+ opts.banner = <<~EOM
83
+ Usage: dead_end <file> [options]
84
+
85
+ Parses a ruby source file and searches for syntax error(s) such as
86
+ unexpected `end', expecting end-of-input.
87
+
88
+ Example:
89
+
90
+ $ dead_end dog.rb
91
+
92
+ # ...
93
+
94
+ ❯ 10 defdog
95
+ ❯ 15 end
96
+
97
+ ENV options:
98
+
99
+ DEAD_END_RECORD_DIR=<dir>
100
+
101
+ Records the steps used to search for a syntax error
102
+ to the given directory
103
+
104
+ Options:
105
+ EOM
106
+
107
+ opts.version = DeadEnd::VERSION
108
+
109
+ opts.on("--help", "Help - displays this message") do |v|
110
+ @io.puts opts
111
+ options[:exit] = true
112
+ @exit_obj.exit
113
+ end
114
+
115
+ opts.on("--record <dir>", "Records the steps used to search for a syntax error to the given directory") do |v|
116
+ options[:record_dir] = v
117
+ end
118
+
119
+ opts.on("--terminal", "Enable terminal highlighting") do |v|
120
+ options[:terminal] = true
121
+ end
122
+
123
+ opts.on("--no-terminal", "Disable terminal highlighting") do |v|
124
+ options[:terminal] = false
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -70,8 +70,24 @@ module DeadEnd
70
70
  end
71
71
 
72
72
  def valid?
73
- return @valid if @valid != UNSET
74
- @valid = DeadEnd.valid?(to_s)
73
+ if @valid == UNSET
74
+ # Performance optimization
75
+ #
76
+ # If all the lines were previously hidden
77
+ # and we expand to capture additional empty
78
+ # lines then the result cannot be invalid
79
+ #
80
+ # That means there's no reason to re-check all
81
+ # lines with ripper (which is expensive).
82
+ # Benchmark in commit message
83
+ @valid = if lines.all? { |l| l.hidden? || l.empty? }
84
+ true
85
+ else
86
+ DeadEnd.valid?(lines.map(&:original).join)
87
+ end
88
+ else
89
+ @valid
90
+ end
75
91
  end
76
92
 
77
93
  def to_s