rails5-spec-converter 1.0.15 → 1.0.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8f809b54f88c799e62a3da375d7e1c6386a24e71
4
- data.tar.gz: 853ab71eb42ad2ff5f6834f25596e6f92a45a1d9
3
+ metadata.gz: da659c54addaff82abd519274ed54477c401438a
4
+ data.tar.gz: 217e06555325fb97b3e10fdfc8cae7c44f096de5
5
5
  SHA512:
6
- metadata.gz: 169d693d60ed16a8f81b30cf0e2e77d1cb019451b0ab64b5224bfaf2b3ed9c3a610fde5723a8d322b87c14aa8d70b8f9fb0943e1d979dbf1f1a5d633decd3f70
7
- data.tar.gz: b2683718f15845812229c662c977302d58bcfa18a1d47b1efe95cf679194b5678fae3b9236d66f7fd962366c251d738f8787c85f6e1c8efe2421a3c02b9f3d73
6
+ metadata.gz: 1c2ced79435fb5d7dd440e863c46a49be07e15a9ef648173342e0680235625e4be3011890ff3afd5fd1e69acef81b6d3f98d2216346de2646db51329d1a47454
7
+ data.tar.gz: 2c478082acb31b1db660f1c35ae12e6a376921ab8acd81e089f7bdf38d6253027a594a8e0ee34550cbc90d497ed50578a8259271125d1408f1e97d2626f26f20
@@ -0,0 +1,256 @@
1
+ require 'rails5/spec_converter/node_textifier'
2
+
3
+ class HashRewriter
4
+ # technically format is special
5
+ ALLOWED_KWARG_KEYS = %i(params session flash method body xhr format)
6
+
7
+ attr_reader :hash_node, :original_indent
8
+
9
+ def initialize(content:, hash_node:, original_indent:, options:)
10
+ @options = options
11
+ @content = content
12
+ @hash_node = hash_node
13
+ @original_indent = original_indent
14
+ @textifier = NodeTextifier.new(@content)
15
+ partition_params(@hash_node)
16
+ end
17
+
18
+ def rewritten_params_hash
19
+ return if @pairs_that_belong_in_params.length == 0
20
+
21
+ rewritten_hashes = []
22
+
23
+ warn_if_inconsistent_indentation
24
+
25
+ if multiline? && should_wrap_rewritten_hash_in_curly_braces?
26
+ params_hash = restring_hash(
27
+ @pairs_that_belong_in_params,
28
+ joiner: ",\n"
29
+ )
30
+
31
+ other_hash = restring_hash(
32
+ @pairs_that_belong_outside_params,
33
+ joiner: ",\n"
34
+ )
35
+
36
+ optional_comma = has_trailing_comma?(hash_node) ? ',' : ''
37
+ new_wrapped_hash_content = wrap_and_indent(
38
+ "{",
39
+ "}",
40
+ [
41
+ wrap_and_indent(
42
+ "params: {",
43
+ "}#{optional_comma}",
44
+ params_hash,
45
+ @options.indent
46
+ ),
47
+ "#{other_hash}"
48
+ ].join("\n"),
49
+ @options.indent
50
+ )
51
+ return add_indent(new_wrapped_hash_content, original_indent, skip_first_line: true)
52
+ elsif multiline? && should_try_to_rewrite_multiline_hash?
53
+ params_hash = appropriately_indented_params_hash(
54
+ pairs: @pairs_that_belong_in_params
55
+ )
56
+
57
+ rewritten_hashes << "params: #{params_hash}"
58
+ else
59
+ curly_sep = determine_curly_sep(hash_node)
60
+ rewritten_hashes << "params: {#{curly_sep}#{restring_hash(@pairs_that_belong_in_params)}#{curly_sep}}"
61
+ end
62
+
63
+ if has_keys_outside_params?
64
+ rewritten_hashes << restring_hash(
65
+ @pairs_that_belong_outside_params,
66
+ joiner: first_joiner_between_pairs
67
+ )
68
+ end
69
+
70
+ rewritten_hashes.join(first_joiner_between_pairs)
71
+ end
72
+
73
+ def should_rewrite_hash?
74
+ @pairs_that_belong_in_params.length > 0
75
+ end
76
+
77
+ def has_keys_outside_params?
78
+ @pairs_that_belong_outside_params.length > 0
79
+ end
80
+
81
+ def should_wrap_rewritten_hash_in_curly_braces?
82
+ multiline? && has_keys_outside_params? && @textifier.node_to_string(hash_node) =~ /^{\n/
83
+ end
84
+
85
+ def should_try_to_rewrite_multiline_hash?
86
+ return false unless multiline?
87
+ return true unless first_joiner_between_pairs
88
+ first_joiner_between_pairs =~ /\n/
89
+ end
90
+
91
+ private
92
+
93
+ def partition_params(hash_node)
94
+ @pairs_that_belong_in_params = []
95
+ @pairs_that_belong_outside_params = []
96
+
97
+ hash_node.children.each do |pair|
98
+ key = pair.children[0].children[0]
99
+
100
+ if ALLOWED_KWARG_KEYS.include?(key)
101
+ @pairs_that_belong_outside_params << pair
102
+ else
103
+ @pairs_that_belong_in_params << pair
104
+ end
105
+ end
106
+ end
107
+
108
+ def has_trailing_comma?(hash_node)
109
+ @textifier.text_after_last_pair(hash_node) =~ /,/
110
+ end
111
+
112
+ def indent_before_first_pair(hash_node)
113
+ return nil unless hash_node.children.length > 0
114
+
115
+ extract_indent(@textifier.text_before_first_pair(hash_node))
116
+ end
117
+
118
+ def indent_after_last_pair(hash_node)
119
+ return nil unless hash_node.children.length > 0
120
+
121
+ extract_indent(@textifier.text_after_last_pair(hash_node))
122
+ end
123
+
124
+ def multiline?
125
+ @textifier.node_to_string(hash_node).include?("\n")
126
+ end
127
+
128
+ def indent_of_first_value_if_multiline(hash_node)
129
+ return nil if hash_node.children.length == 0
130
+ return nil unless hash_node.children[0].pair_type?
131
+
132
+ first_value = hash_node.children[0].children[1]
133
+ return nil unless first_value.hash_type? || first_value.array_type?
134
+ value_str_lines = @textifier.node_to_string(first_value).split("\n")
135
+ return nil if value_str_lines.length == 1
136
+ return nil unless value_str_lines[0].match(/[\s\[{]/)
137
+
138
+ value_str_lines[1].match(/^(\s*)/)[1].sub(original_indent, '')
139
+ end
140
+
141
+ def additional_indent(hash_node)
142
+ return nil if indent_before_first_pair(hash_node)
143
+
144
+ joiner = first_joiner_between_pairs
145
+ joiner && joiner.include?("\n") ? @options.indent : nil
146
+ end
147
+
148
+ def existing_indent(hash_node)
149
+ text_before_hash = @textifier.text_before_node(hash_node)
150
+ whitespace_indent = extract_indent(text_before_hash)
151
+ return whitespace_indent if whitespace_indent
152
+
153
+ return indent_before_first_pair(hash_node) if indent_before_first_pair(hash_node)
154
+
155
+ joiner = first_joiner_between_pairs
156
+ extract_indent(joiner) || ''
157
+ end
158
+
159
+ def no_space_after_curly?(hash_node)
160
+ hash_node.parent.loc.expression.source.match(/{\S/)
161
+ end
162
+
163
+ def texts_between_pairs
164
+ return @texts_between if @texts_between
165
+
166
+ @texts_between = []
167
+ hash_node.children[0..-2].each_with_index do |pair, index|
168
+ next_pair = hash_node.children[index + 1]
169
+ @texts_between << @textifier.text_between_siblings(pair, next_pair)
170
+ end
171
+ @texts_between
172
+ end
173
+
174
+ def first_joiner_between_pairs
175
+ texts_between_pairs[0]
176
+ end
177
+
178
+ def has_inconsistent_indentation?
179
+ texts_between_pairs.uniq.length > 1
180
+ end
181
+
182
+ def warn_if_inconsistent_indentation
183
+ return unless has_inconsistent_indentation?
184
+
185
+ log "Inconsistent whitespace between hash pairs, using the first separator (#{texts_between_pairs[0].inspect})."
186
+ log "Seen when processing this expression: \n```\n#{hash_node.loc.expression.source}\n```\n\n"
187
+ end
188
+
189
+ def appropriately_indented_params_hash(pairs:)
190
+ outer_indent = existing_indent(hash_node)
191
+ middle_indent = indent_of_first_value_if_multiline(hash_node)
192
+ inner_indent = additional_indent(hash_node)
193
+
194
+ restrung_hash = restring_hash(
195
+ pairs,
196
+ indent: outer_indent + (inner_indent || ''),
197
+ joiner: ",\n"
198
+ )
199
+ if middle_indent
200
+ restrung_hash = original_indent + add_indent(restrung_hash, middle_indent)
201
+ end
202
+ final_brace_indent = if middle_indent
203
+ original_indent
204
+ else
205
+ indent_after_last_pair(hash_node) || outer_indent
206
+ end
207
+ "{\n#{restrung_hash}\n#{final_brace_indent}}"
208
+ end
209
+
210
+ def determine_curly_sep(hash_node)
211
+ return ' ' if @options.hash_spacing == true
212
+ return '' if @options.hash_spacing == false
213
+
214
+ no_space_after_curly?(hash_node) ? '' : ' '
215
+ end
216
+
217
+ def restring_hash(pairs, joiner: ", ", indent: '')
218
+ hash_string = pairs.map { |pair| "#{indent}#{pair.loc.expression.source}" }.join(joiner)
219
+ if has_trailing_comma?(hash_node)
220
+ hash_string + ','
221
+ else
222
+ hash_string
223
+ end
224
+ end
225
+
226
+ def add_indent_and_curly_braces(str, indent)
227
+ "{\n#{add_indent(str, indent)}\n}"
228
+ end
229
+
230
+ def wrap_and_indent(start_string, end_string, inner_string, indent)
231
+ "#{start_string}\n#{add_indent(inner_string, indent)}\n#{end_string}"
232
+ end
233
+
234
+ def add_indent(str, indent, skip_first_line: false)
235
+ str.split("\n").each_with_index.map do |line, index|
236
+ if index.zero? && skip_first_line
237
+ line
238
+ else
239
+ indent + line
240
+ end
241
+ end.join("\n")
242
+ end
243
+
244
+ def extract_indent(str)
245
+ return unless str
246
+
247
+ match = str.match("\n(\s*)")
248
+ match[1] if match
249
+ end
250
+
251
+ def log(str)
252
+ return if @options.quiet?
253
+
254
+ puts str
255
+ end
256
+ end
@@ -0,0 +1,28 @@
1
+ class NodeTextifier
2
+ def initialize(content)
3
+ @content = content
4
+ end
5
+
6
+ def text_before_first_pair(hash_node)
7
+ @content[hash_node.loc.expression.begin_pos...hash_node.children.first.loc.expression.begin_pos]
8
+ end
9
+
10
+ def text_after_last_pair(hash_node)
11
+ @content[hash_node.children.last.loc.expression.end_pos...hash_node.loc.expression.end_pos]
12
+ end
13
+
14
+ def text_before_node(node)
15
+ previous_sibling = node.parent.children[node.sibling_index - 1]
16
+ return nil unless previous_sibling.loc.expression
17
+
18
+ text_between_siblings(previous_sibling, node)
19
+ end
20
+
21
+ def text_between_siblings(node1, node2)
22
+ @content[node1.loc.expression.end_pos...node2.loc.expression.begin_pos]
23
+ end
24
+
25
+ def node_to_string(node)
26
+ @content[node.loc.expression.begin_pos...node.loc.expression.end_pos]
27
+ end
28
+ end
@@ -1,13 +1,11 @@
1
1
  require 'parser/current'
2
2
  require 'astrolabe/builder'
3
3
  require 'rails5/spec_converter/text_transformer_options'
4
- require 'rails5/spec_converter/hash_node_text_analyzer'
4
+ require 'rails5/spec_converter/hash_rewriter'
5
5
 
6
6
  module Rails5
7
7
  module SpecConverter
8
8
  HTTP_VERBS = %i(get post put patch delete)
9
- # technically format is special
10
- ALLOWED_KWARG_KEYS = %i(params session flash method body xhr format)
11
9
 
12
10
  class TextTransformer
13
11
  def initialize(content, options = TextTransformerOptions.new)
@@ -41,10 +39,17 @@ module Rails5
41
39
  next unless @options.wrap_ambiguous_params?
42
40
  end
43
41
 
44
- write_params_hash(
42
+ hash_rewriter = HashRewriter.new(
43
+ content: @content,
44
+ options: @options,
45
45
  hash_node: args[0],
46
46
  original_indent: node.loc.expression.source_line.match(/^(\s*)/)[1]
47
47
  )
48
+
49
+ @source_rewriter.replace(
50
+ args[0].loc.expression,
51
+ hash_rewriter.rewritten_params_hash
52
+ ) if hash_rewriter.should_rewrite_hash?
48
53
  end
49
54
  else
50
55
  warn_about_ambiguous_params(node) if @options.warn_about_ambiguous_params?
@@ -81,156 +86,10 @@ module Rails5
81
86
  hash_node.children.any? { |node| node.kwsplat_type? }
82
87
  end
83
88
 
84
- def has_trailing_comma?(hash_node)
85
- HashNodeTextAnalyzer.new(@content, hash_node).text_after_last_pair =~ /,/
86
- end
87
-
88
89
  def has_key?(hash_node, key)
89
90
  hash_node.children.any? { |pair| pair.children[0].children[0] == key }
90
91
  end
91
92
 
92
- def write_params_hash(hash_node:, original_indent: )
93
- pairs_that_belong_in_params, pairs_that_belong_outside_params = partition_params(hash_node)
94
- use_trailing_comma = has_trailing_comma?(hash_node)
95
-
96
- if pairs_that_belong_in_params.length > 0
97
- joiner = joiner_between_pairs(hash_node)
98
- params_hash = appropriately_spaced_params_hash(
99
- hash_node: hash_node,
100
- pairs: pairs_that_belong_in_params,
101
- original_indent: original_indent,
102
- use_trailing_comma: use_trailing_comma
103
- )
104
-
105
- rewritten_hashes = ["params: #{params_hash}"]
106
- if pairs_that_belong_outside_params.length > 0
107
- rewritten_hashes << restring_hash(
108
- pairs_that_belong_outside_params,
109
- joiner: joiner,
110
- use_trailing_comma: use_trailing_comma
111
- )
112
- end
113
- @source_rewriter.replace(
114
- hash_node.loc.expression,
115
- rewritten_hashes.join(joiner)
116
- )
117
- end
118
- end
119
-
120
- def partition_params(hash_node)
121
- pairs_that_belong_in_params = []
122
- pairs_that_belong_outside_params = []
123
-
124
- hash_node.children.each do |pair|
125
- key = pair.children[0].children[0]
126
-
127
- if ALLOWED_KWARG_KEYS.include?(key)
128
- pairs_that_belong_outside_params << pair
129
- else
130
- pairs_that_belong_in_params << pair
131
- end
132
- end
133
- return pairs_that_belong_in_params, pairs_that_belong_outside_params
134
- end
135
-
136
- def indent_before_first_pair(hash_node)
137
- return nil unless hash_node.children.length > 0
138
-
139
- extract_indent(HashNodeTextAnalyzer.new(@content, hash_node).text_before_first_pair)
140
- end
141
-
142
- def indent_after_last_pair(hash_node)
143
- return nil unless hash_node.children.length > 0
144
-
145
- extract_indent(HashNodeTextAnalyzer.new(@content, hash_node).text_after_last_pair)
146
- end
147
-
148
- def indent_of_first_value_if_multiline(hash_node, original_indent)
149
- return nil if hash_node.children.length == 0
150
- return nil unless hash_node.children[0].pair_type?
151
-
152
- first_value = hash_node.children[0].children[1]
153
- return nil unless first_value.hash_type? || first_value.array_type?
154
- value_str_lines = node_to_string(first_value).split("\n")
155
- return nil if value_str_lines.length == 1
156
- return nil unless value_str_lines[0].match(/[\s\[{]/)
157
-
158
- value_str_lines[1].match(/^(\s*)/)[1].sub(original_indent, '')
159
- end
160
-
161
- def additional_indent(hash_node)
162
- return nil if indent_before_first_pair(hash_node)
163
-
164
- joiner = joiner_between_pairs(hash_node)
165
- joiner && joiner.include?("\n") ? @options.indent : nil
166
- end
167
-
168
- def existing_indent(hash_node)
169
- text_before_hash = text_before_node(hash_node)
170
- whitespace_indent = extract_indent(text_before_hash)
171
- return whitespace_indent if whitespace_indent
172
-
173
- return indent_before_first_pair(hash_node) if indent_before_first_pair(hash_node)
174
-
175
- joiner = joiner_between_pairs(hash_node)
176
- extract_indent(joiner) || ''
177
- end
178
-
179
- def no_space_after_curly?(hash_node)
180
- hash_node.parent.loc.expression.source.match(/{\S/)
181
- end
182
-
183
- def joiner_between_pairs(hash_node)
184
- texts_between = []
185
- hash_node.children[0..-2].each_with_index do |pair, index|
186
- next_pair = hash_node.children[index + 1]
187
- texts_between << text_between_siblings(pair, next_pair)
188
- end
189
- if texts_between.uniq.length > 1
190
- log "Inconsistent whitespace between hash pairs, using the first separator (#{texts_between[0].inspect})."
191
- log "Seen when processing this expression: \n```\n#{hash_node.loc.expression.source}\n```\n\n"
192
- end
193
- texts_between[0]
194
- end
195
-
196
- def text_between_siblings(node1, node2)
197
- @content[node1.loc.expression.end_pos...node2.loc.expression.begin_pos]
198
- end
199
-
200
- def appropriately_spaced_params_hash(hash_node:, pairs:, original_indent:, use_trailing_comma:)
201
- outer_indent = existing_indent(hash_node)
202
- middle_indent = indent_of_first_value_if_multiline(hash_node, original_indent)
203
- inner_indent = additional_indent(hash_node)
204
-
205
- if inner_indent || middle_indent || indent_before_first_pair(hash_node)
206
- restrung_hash = restring_hash(
207
- pairs,
208
- indent: outer_indent + (inner_indent || ''),
209
- joiner: ",\n",
210
- use_trailing_comma: use_trailing_comma
211
- )
212
- if middle_indent
213
- restrung_hash = original_indent + add_indent(restrung_hash, middle_indent)
214
- end
215
- final_brace_indent = if middle_indent
216
- original_indent
217
- else
218
- indent_after_last_pair(hash_node) || outer_indent
219
- end
220
- "{\n#{restrung_hash}\n#{final_brace_indent}}"
221
- else
222
- curly_sep = determine_curly_sep(hash_node)
223
- "{#{curly_sep}#{restring_hash(pairs, use_trailing_comma: use_trailing_comma)}#{curly_sep}}"
224
- end
225
- end
226
-
227
- def determine_curly_sep(hash_node)
228
- return ' ' if @options.hash_spacing == true
229
- return '' if @options.hash_spacing == false
230
-
231
- no_space_after_curly?(hash_node) ? '' : ' '
232
- end
233
-
234
93
  def wrap_arg(node, key)
235
94
  node_loc = node.loc.expression
236
95
  node_source = node_loc.source
@@ -240,37 +99,6 @@ module Rails5
240
99
  @source_rewriter.replace(node_loc, "#{key}: #{node_source}")
241
100
  end
242
101
 
243
- def restring_hash(pairs, joiner: ", ", indent: '', use_trailing_comma: false)
244
- hash_string = pairs.map { |pair| "#{indent}#{pair.loc.expression.source}" }.join(joiner)
245
- if use_trailing_comma
246
- hash_string + ','
247
- else
248
- hash_string
249
- end
250
- end
251
-
252
- def add_indent(str, indent)
253
- str.split("\n").map { |line| indent + line }.join("\n")
254
- end
255
-
256
- def extract_indent(str)
257
- return unless str
258
-
259
- match = str.match("\n(\s*)")
260
- match[1] if match
261
- end
262
-
263
- def text_before_node(node)
264
- previous_sibling = node.parent.children[node.sibling_index - 1]
265
- return nil unless previous_sibling.loc.expression
266
-
267
- text_between_siblings(previous_sibling, node)
268
- end
269
-
270
- def node_to_string(node)
271
- @content[node.loc.expression.begin_pos...node.loc.expression.end_pos]
272
- end
273
-
274
102
  def warn_about_ambiguous_params(node)
275
103
  log "Ambiguous params found"
276
104
  log "#{@options.file_path}:#{node.loc.line}" if @options.file_path
@@ -1,5 +1,5 @@
1
1
  module Rails5
2
2
  module SpecConverter
3
- VERSION = "1.0.15"
3
+ VERSION = "1.0.16"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails5-spec-converter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.15
4
+ version: 1.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Travis Grathwell
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-12 00:00:00.000000000 Z
11
+ date: 2016-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -116,7 +116,8 @@ files:
116
116
  - exe/rails5-spec-converter
117
117
  - lib/rails5/spec_converter.rb
118
118
  - lib/rails5/spec_converter/cli.rb
119
- - lib/rails5/spec_converter/hash_node_text_analyzer.rb
119
+ - lib/rails5/spec_converter/hash_rewriter.rb
120
+ - lib/rails5/spec_converter/node_textifier.rb
120
121
  - lib/rails5/spec_converter/text_transformer.rb
121
122
  - lib/rails5/spec_converter/text_transformer_options.rb
122
123
  - lib/rails5/spec_converter/version.rb
@@ -1,16 +0,0 @@
1
- class HashNodeTextAnalyzer
2
- attr_reader :content, :hash_node
3
-
4
- def initialize(content, hash_node)
5
- @content = content
6
- @hash_node = hash_node
7
- end
8
-
9
- def text_before_first_pair
10
- content[hash_node.loc.expression.begin_pos...hash_node.children.first.loc.expression.begin_pos]
11
- end
12
-
13
- def text_after_last_pair
14
- content[hash_node.children.last.loc.expression.end_pos...hash_node.loc.expression.end_pos]
15
- end
16
- end