unparser 0.4.3 → 0.4.8
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +90 -0
- data/.rubocop.yml +122 -5
- data/Changelog.md +24 -0
- data/Gemfile +3 -5
- data/Gemfile.lock +55 -122
- data/README.md +1 -3
- data/config/flay.yml +1 -1
- data/lib/unparser.rb +21 -3
- data/lib/unparser/ast.rb +1 -1
- data/lib/unparser/ast/local_variable_scope.rb +6 -6
- data/lib/unparser/cli.rb +65 -45
- data/lib/unparser/{cli/color.rb → color.rb} +0 -10
- data/lib/unparser/constants.rb +1 -1
- data/lib/unparser/diff.rb +115 -0
- data/lib/unparser/dsl.rb +1 -1
- data/lib/unparser/emitter.rb +4 -5
- data/lib/unparser/emitter/argument.rb +9 -13
- data/lib/unparser/emitter/literal/primitive.rb +1 -1
- data/lib/unparser/emitter/literal/range.rb +1 -1
- data/lib/unparser/node_helpers.rb +4 -2
- data/lib/unparser/preprocessor.rb +1 -1
- data/lib/unparser/validation.rb +149 -0
- data/spec/integration/unparser/corpus_spec.rb +33 -19
- data/spec/integrations.yml +6 -1
- data/spec/spec_helper.rb +26 -4
- data/spec/unit/unparser/color_spec.rb +40 -0
- data/spec/unit/unparser/diff_spec.rb +189 -0
- data/spec/unit/unparser/validation_spec.rb +327 -0
- data/spec/unit/unparser_spec.rb +88 -9
- data/unparser.gemspec +12 -7
- metadata +104 -29
- data/.circleci/config.yml +0 -41
- data/config/rubocop.yml +0 -122
- data/lib/unparser/cli/differ.rb +0 -152
- data/lib/unparser/cli/source.rb +0 -267
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Unparser::Diff do
|
4
|
+
let(:object) { described_class }
|
5
|
+
|
6
|
+
describe '.build' do
|
7
|
+
|
8
|
+
subject { object.build(old_string, new_string) }
|
9
|
+
|
10
|
+
let(:old_string) { "foo\nbar" }
|
11
|
+
let(:new_string) { "bar\nbaz" }
|
12
|
+
|
13
|
+
it { should eql(described_class.new(%w[foo bar], %w[bar baz])) }
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#colorized_diff' do
|
18
|
+
let(:object) { described_class.new(old, new) }
|
19
|
+
|
20
|
+
subject { object.colorized_diff }
|
21
|
+
|
22
|
+
context 'when there is a diff at begin of hunk' do
|
23
|
+
let(:old) { %w[foo bar] }
|
24
|
+
let(:new) { %w[baz bar] }
|
25
|
+
|
26
|
+
let(:expectation) do
|
27
|
+
[
|
28
|
+
"@@ -1,3 +1,3 @@\n",
|
29
|
+
Unparser::Color::RED.format("-foo\n"),
|
30
|
+
Unparser::Color::GREEN.format("+baz\n"),
|
31
|
+
" bar\n"
|
32
|
+
].join
|
33
|
+
end
|
34
|
+
|
35
|
+
it { should eql(expectation) }
|
36
|
+
|
37
|
+
it_should_behave_like 'an idempotent method'
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when there is no diff' do
|
41
|
+
let(:old) { '' }
|
42
|
+
let(:new) { '' }
|
43
|
+
|
44
|
+
it { should be(nil) }
|
45
|
+
|
46
|
+
it_should_behave_like 'an idempotent method'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#diff' do
|
51
|
+
let(:object) { described_class.new(old, new) }
|
52
|
+
|
53
|
+
subject { object.diff }
|
54
|
+
|
55
|
+
context 'when there is a diff at begin and end' do
|
56
|
+
let(:old) { %w[foo bar foo] }
|
57
|
+
let(:new) { %w[baz bar baz] }
|
58
|
+
|
59
|
+
let(:expectation) do
|
60
|
+
<<~STR
|
61
|
+
@@ -1,4 +1,4 @@
|
62
|
+
-foo
|
63
|
+
+baz
|
64
|
+
bar
|
65
|
+
-foo
|
66
|
+
+baz
|
67
|
+
STR
|
68
|
+
end
|
69
|
+
|
70
|
+
it { should eql(expectation) }
|
71
|
+
|
72
|
+
it_should_behave_like 'an idempotent method'
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when there is a diff at begin of hunk' do
|
76
|
+
let(:old) { %w[foo bar] }
|
77
|
+
let(:new) { %w[baz bar] }
|
78
|
+
|
79
|
+
let(:expectation) do
|
80
|
+
<<~STR
|
81
|
+
@@ -1,3 +1,3 @@
|
82
|
+
-foo
|
83
|
+
+baz
|
84
|
+
bar
|
85
|
+
STR
|
86
|
+
end
|
87
|
+
|
88
|
+
it { should eql(expectation) }
|
89
|
+
|
90
|
+
it_should_behave_like 'an idempotent method'
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when there is a diff NOT at begin of hunk' do
|
94
|
+
let(:old) { %w[foo bar] }
|
95
|
+
let(:new) { %w[foo baz bar] }
|
96
|
+
|
97
|
+
let(:expectation) do
|
98
|
+
<<~STR
|
99
|
+
@@ -1,3 +1,4 @@
|
100
|
+
foo
|
101
|
+
+baz
|
102
|
+
bar
|
103
|
+
STR
|
104
|
+
end
|
105
|
+
|
106
|
+
it { should eql(expectation) }
|
107
|
+
|
108
|
+
it_should_behave_like 'an idempotent method'
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'when the diff has a long context at begin' do
|
112
|
+
let(:old) { %w[foo bar baz boz a b c] }
|
113
|
+
let(:new) { %w[foo bar baz boz a b c other] }
|
114
|
+
|
115
|
+
let(:expectation) do
|
116
|
+
<<~STR
|
117
|
+
@@ -1,8 +1,9 @@
|
118
|
+
foo
|
119
|
+
bar
|
120
|
+
baz
|
121
|
+
boz
|
122
|
+
a
|
123
|
+
b
|
124
|
+
c
|
125
|
+
+other
|
126
|
+
STR
|
127
|
+
end
|
128
|
+
|
129
|
+
it { should eql(expectation) }
|
130
|
+
|
131
|
+
it_should_behave_like 'an idempotent method'
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when the diff has a long context at end, deleting' do
|
135
|
+
let(:old) { %w[other foo bar baz boz a b c] }
|
136
|
+
let(:new) { %w[foo bar baz boz a b c] }
|
137
|
+
|
138
|
+
let(:expectation) do
|
139
|
+
<<~STR
|
140
|
+
@@ -1,9 +1,8 @@
|
141
|
+
-other
|
142
|
+
foo
|
143
|
+
bar
|
144
|
+
baz
|
145
|
+
boz
|
146
|
+
a
|
147
|
+
b
|
148
|
+
c
|
149
|
+
STR
|
150
|
+
end
|
151
|
+
|
152
|
+
it { should eql(expectation) }
|
153
|
+
|
154
|
+
it_should_behave_like 'an idempotent method'
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'when the diff has a long context at end, inserting' do
|
158
|
+
let(:old) { %w[foo bar baz boz a b c] }
|
159
|
+
let(:new) { %w[other foo bar baz boz a b c] }
|
160
|
+
|
161
|
+
let(:expectation) do
|
162
|
+
<<~STR
|
163
|
+
@@ -1,8 +1,9 @@
|
164
|
+
+other
|
165
|
+
foo
|
166
|
+
bar
|
167
|
+
baz
|
168
|
+
boz
|
169
|
+
a
|
170
|
+
b
|
171
|
+
c
|
172
|
+
STR
|
173
|
+
end
|
174
|
+
|
175
|
+
it { should eql(expectation) }
|
176
|
+
|
177
|
+
it_should_behave_like 'an idempotent method'
|
178
|
+
end
|
179
|
+
|
180
|
+
context 'when there is no diff' do
|
181
|
+
let(:old) { '' }
|
182
|
+
let(:new) { '' }
|
183
|
+
|
184
|
+
it { should be(nil) }
|
185
|
+
|
186
|
+
it_should_behave_like 'an idempotent method'
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,327 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Unparser::Validation do
|
4
|
+
let(:object) do
|
5
|
+
described_class.new(
|
6
|
+
identification: identification,
|
7
|
+
generated_node: generated_node,
|
8
|
+
generated_source: generated_source,
|
9
|
+
original_node: original_node,
|
10
|
+
original_source: original_source
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def right(value)
|
15
|
+
MPrelude::Either::Right.new(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def left(value)
|
19
|
+
MPrelude::Either::Left.new(value)
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:generated_node) { right(s(:send, s(:int, 1), :foo)) }
|
23
|
+
let(:generated_source) { right('1.foo') }
|
24
|
+
let(:identification) { 'example-identification' }
|
25
|
+
let(:original_node) { right(s(:send, s(:int, 1), :foo)) }
|
26
|
+
let(:original_source) { right('1.foo') }
|
27
|
+
|
28
|
+
let(:exception) do
|
29
|
+
left(
|
30
|
+
instance_double(
|
31
|
+
RuntimeError,
|
32
|
+
message: 'foo',
|
33
|
+
backtrace: Array.new(21, &'line-%02d'.method(:%))
|
34
|
+
)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:exception_report) do
|
39
|
+
<<~'REPORT'.strip
|
40
|
+
#<InstanceDouble(RuntimeError) (anonymous)>
|
41
|
+
line-00
|
42
|
+
line-01
|
43
|
+
line-02
|
44
|
+
line-03
|
45
|
+
line-04
|
46
|
+
line-05
|
47
|
+
line-06
|
48
|
+
line-07
|
49
|
+
line-08
|
50
|
+
line-09
|
51
|
+
line-10
|
52
|
+
line-11
|
53
|
+
line-12
|
54
|
+
line-13
|
55
|
+
line-14
|
56
|
+
line-15
|
57
|
+
line-16
|
58
|
+
line-17
|
59
|
+
line-18
|
60
|
+
line-19
|
61
|
+
REPORT
|
62
|
+
end
|
63
|
+
|
64
|
+
def report
|
65
|
+
object.report
|
66
|
+
end
|
67
|
+
|
68
|
+
shared_examples 'not successful' do
|
69
|
+
it 'is not successful' do
|
70
|
+
expect(object.success?).to be(false)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'on success' do
|
75
|
+
it 'is successful' do
|
76
|
+
expect(object.success?).to be(true)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'returns expected report' do
|
80
|
+
expect(report).to eql(<<~'REPORT'.strip)
|
81
|
+
example-identification
|
82
|
+
Original-Source:
|
83
|
+
1.foo
|
84
|
+
Generated-Source:
|
85
|
+
1.foo
|
86
|
+
Original-Node:
|
87
|
+
(send
|
88
|
+
(int 1) :foo)
|
89
|
+
Generated-Node:
|
90
|
+
(send
|
91
|
+
(int 1) :foo)
|
92
|
+
REPORT
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'on failing to generate original source with exception' do
|
97
|
+
let(:original_source) { exception }
|
98
|
+
|
99
|
+
include_examples 'not successful'
|
100
|
+
|
101
|
+
it 'returns expected report' do
|
102
|
+
expect(report).to eql(<<~REPORT.strip)
|
103
|
+
example-identification
|
104
|
+
Original-Source:
|
105
|
+
#{exception_report}
|
106
|
+
Generated-Source:
|
107
|
+
1.foo
|
108
|
+
Original-Node:
|
109
|
+
(send
|
110
|
+
(int 1) :foo)
|
111
|
+
Generated-Node:
|
112
|
+
(send
|
113
|
+
(int 1) :foo)
|
114
|
+
REPORT
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'on failing to parse generated source due precondition error' do
|
119
|
+
let(:generated_node) { left(nil) }
|
120
|
+
|
121
|
+
include_examples 'not successful'
|
122
|
+
|
123
|
+
it 'returns expected report' do
|
124
|
+
expect(report).to eql(<<~REPORT.strip)
|
125
|
+
example-identification
|
126
|
+
Original-Source:
|
127
|
+
1.foo
|
128
|
+
Generated-Source:
|
129
|
+
1.foo
|
130
|
+
Original-Node:
|
131
|
+
(send
|
132
|
+
(int 1) :foo)
|
133
|
+
Generated-Node:
|
134
|
+
undefined
|
135
|
+
REPORT
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'on failing to parse original source' do
|
140
|
+
let(:original_node) { exception }
|
141
|
+
|
142
|
+
include_examples 'not successful'
|
143
|
+
|
144
|
+
it 'returns expected report' do
|
145
|
+
expect(report).to eql(<<~REPORT.strip)
|
146
|
+
example-identification
|
147
|
+
Original-Source:
|
148
|
+
1.foo
|
149
|
+
Generated-Source:
|
150
|
+
1.foo
|
151
|
+
Original-Node:
|
152
|
+
#{exception_report}
|
153
|
+
Generated-Node:
|
154
|
+
(send
|
155
|
+
(int 1) :foo)
|
156
|
+
REPORT
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'on failing to generate generated source' do
|
161
|
+
let(:generated_source) { exception }
|
162
|
+
|
163
|
+
include_examples 'not successful'
|
164
|
+
|
165
|
+
it 'returns expected report' do
|
166
|
+
expect(report).to eql(<<~REPORT.strip)
|
167
|
+
example-identification
|
168
|
+
Original-Source:
|
169
|
+
1.foo
|
170
|
+
Generated-Source:
|
171
|
+
#{exception_report}
|
172
|
+
Original-Node:
|
173
|
+
(send
|
174
|
+
(int 1) :foo)
|
175
|
+
Generated-Node:
|
176
|
+
(send
|
177
|
+
(int 1) :foo)
|
178
|
+
REPORT
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'on failing to parse generated source' do
|
183
|
+
let(:generated_node) { exception }
|
184
|
+
|
185
|
+
include_examples 'not successful'
|
186
|
+
|
187
|
+
it 'returns expected report' do
|
188
|
+
expect(report).to eql(<<~REPORT.strip)
|
189
|
+
example-identification
|
190
|
+
Original-Source:
|
191
|
+
1.foo
|
192
|
+
Generated-Source:
|
193
|
+
1.foo
|
194
|
+
Original-Node:
|
195
|
+
(send
|
196
|
+
(int 1) :foo)
|
197
|
+
Generated-Node:
|
198
|
+
#{exception_report}
|
199
|
+
REPORT
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'on generating different node' do
|
204
|
+
let(:generated_node) { right(s(:send, s(:int, 1), :bar)) }
|
205
|
+
|
206
|
+
include_examples 'not successful'
|
207
|
+
|
208
|
+
it 'returns expected report' do
|
209
|
+
diff = [
|
210
|
+
Unparser::Color::NONE.format(" (send\n"),
|
211
|
+
Unparser::Color::RED.format("- (int 1) :foo)\n"),
|
212
|
+
Unparser::Color::GREEN.format("+ (int 1) :bar)\n")
|
213
|
+
]
|
214
|
+
|
215
|
+
expect(report).to eql(<<~'REPORT' + diff.join)
|
216
|
+
example-identification
|
217
|
+
Original-Source:
|
218
|
+
1.foo
|
219
|
+
Generated-Source:
|
220
|
+
1.foo
|
221
|
+
Original-Node:
|
222
|
+
(send
|
223
|
+
(int 1) :foo)
|
224
|
+
Generated-Node:
|
225
|
+
(send
|
226
|
+
(int 1) :bar)
|
227
|
+
Node-Diff:
|
228
|
+
@@ -1,3 +1,3 @@
|
229
|
+
REPORT
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe '.from_path' do
|
234
|
+
def apply
|
235
|
+
described_class.from_path(path)
|
236
|
+
end
|
237
|
+
|
238
|
+
let(:path) { instance_double(Pathname, read: source, to_s: '/some/file') }
|
239
|
+
let(:source) { 'true' }
|
240
|
+
|
241
|
+
it 'returns expected validator' do
|
242
|
+
expect(apply).to eql(
|
243
|
+
described_class.new(
|
244
|
+
generated_node: right(s(:true)),
|
245
|
+
generated_source: right(source),
|
246
|
+
identification: '/some/file',
|
247
|
+
original_node: right(s(:true)),
|
248
|
+
original_source: right(source)
|
249
|
+
)
|
250
|
+
)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
describe '.from_string' do
|
255
|
+
def apply
|
256
|
+
described_class.from_string(source)
|
257
|
+
end
|
258
|
+
|
259
|
+
let(:attributes) do
|
260
|
+
{
|
261
|
+
generated_node: right(s(:true)),
|
262
|
+
generated_source: right(source),
|
263
|
+
identification: '(string)',
|
264
|
+
original_node: right(s(:true)),
|
265
|
+
original_source: right(source)
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
context 'on valid original source' do
|
270
|
+
let(:source) { 'true' }
|
271
|
+
|
272
|
+
it 'returns expected validator' do
|
273
|
+
expect(apply).to eql(described_class.new(attributes))
|
274
|
+
end
|
275
|
+
|
276
|
+
context 'with unparsing error' do
|
277
|
+
let(:exception) { RuntimeError.new('example-error') }
|
278
|
+
|
279
|
+
before do
|
280
|
+
allow(Unparser).to receive(:unparse).and_raise(exception)
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'returns expected validator' do
|
284
|
+
validator = apply
|
285
|
+
|
286
|
+
expect(validator.generated_node).to eql(left(nil))
|
287
|
+
expect(validator.generated_source.from_left.class).to be(RuntimeError)
|
288
|
+
expect(validator.original_source).to eql(right(source))
|
289
|
+
expect(validator.original_node).to eql(right(s(:true)))
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# These are actually specifying the wrong behavior as the normalization is a conceptual mistake
|
295
|
+
# But for now we specify them to get this PR through.
|
296
|
+
#
|
297
|
+
# Removal in followup.
|
298
|
+
context 'on denormalized valid original source' do
|
299
|
+
let(:source) { '(true)' }
|
300
|
+
|
301
|
+
it 'returns expected validator' do
|
302
|
+
expect(apply).to eql(described_class.new(attributes.merge(generated_source: right('true'))))
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
context 'on very denormalized valid original source' do
|
307
|
+
let(:source) { '((true))' }
|
308
|
+
|
309
|
+
it 'returns expected validator' do
|
310
|
+
expect(apply).to eql(described_class.new(attributes.merge(generated_source: right('true'))))
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context 'on invalid original source' do
|
315
|
+
let(:source) { '(' }
|
316
|
+
|
317
|
+
it 'returns expected validator' do
|
318
|
+
validator = apply
|
319
|
+
|
320
|
+
expect(validator.generated_node).to eql(left(nil))
|
321
|
+
expect(validator.generated_source).to eql(left(nil))
|
322
|
+
expect(validator.original_source).to eql(right(source))
|
323
|
+
expect(validator.original_node.from_left.class).to be(Parser::SyntaxError)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|