syntax_tree-haml 1.2.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b81fe1099e22a95015983cabc44340820430e2b1273d89b8042cd5c0799e9eec
4
- data.tar.gz: 81dac915a833b4a8a4fd82e5f14dc1b98b6db5d94733701c57b18cc9d86cd983
3
+ metadata.gz: 3ec66452fbab00bf664cbd1d86b9b351a039eb99ac70547dc2defd72003122da
4
+ data.tar.gz: 5e643711d3d79a8e7c37bc6f17afbb9d855eab2fbf7992f348014cd14ead9490
5
5
  SHA512:
6
- metadata.gz: fd3c147a0e384a3b95196ed051420f8d873c960bc28af8df964b9fa0e5550adcf1072c1f76b4b39675ccdbdc8099fd7fb02ec1e5c6062a028ee4f0317afca19e
7
- data.tar.gz: 764aa4c5a07616dd5a6a43f8dde3efa0b61490573ff414b5c271a2ee25b417def1b8586b7ce7bfaa4d51871bb8bd66356e826fc0131b9cabadf0b31e441a5183
6
+ metadata.gz: 6a55f022617f25d7898f92ebdcca872cd3920e2177144dc63caa117a8a12bf4c15ee9ec9ad5dedd65312b64191fdf40725b6e83bf1733066e6055901bc374699
7
+ data.tar.gz: b06d3310d759f3b11fda480172d66253edc1843740f59aef360a7ca5e7d2130d171ee27d272e73a52038ac50b7f1345dcb0a8a4c89a85c3f4a98914a73aee1c9
@@ -22,7 +22,9 @@ jobs:
22
22
  bundler-cache: true
23
23
  ruby-version: ${{ matrix.ruby }}
24
24
  - name: Test
25
- run: bundle exec rake test
25
+ run: |
26
+ bundle exec rake test
27
+ bundle exec rake stree:check
26
28
  automerge:
27
29
  name: AutoMerge
28
30
  needs: ci
data/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.2.1] - 2022-07-22
10
+
11
+ ### Changed
12
+
13
+ - Fix formatting for when empty `%div` or `%div` with no attributes and just children is present.
14
+
9
15
  ## [1.2.0] - 2022-05-13
10
16
 
11
17
  ### Added
@@ -36,7 +42,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
36
42
 
37
43
  - 🎉 Initial release! 🎉
38
44
 
39
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.2.0...HEAD
45
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.2.1...HEAD
46
+ [1.2.1]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.2.0...v1.2.1
40
47
  [1.2.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.1.0...v1.2.0
41
48
  [1.1.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.0.1...v1.1.0
42
49
  [1.0.1]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.0.0...v1.0.1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree-haml (1.2.0)
4
+ syntax_tree-haml (1.2.1)
5
5
  haml (>= 5.2)
6
6
  prettier_print
7
7
  syntax_tree (>= 2.0.1)
@@ -13,7 +13,7 @@ GEM
13
13
  haml (5.2.2)
14
14
  temple (>= 0.8.0)
15
15
  tilt
16
- minitest (5.15.0)
16
+ minitest (5.16.2)
17
17
  prettier_print (0.1.0)
18
18
  rake (13.0.6)
19
19
  simplecov (0.21.2)
@@ -22,7 +22,7 @@ GEM
22
22
  simplecov_json_formatter (~> 0.1)
23
23
  simplecov-html (0.12.3)
24
24
  simplecov_json_formatter (0.1.4)
25
- syntax_tree (2.5.0)
25
+ syntax_tree (3.2.0)
26
26
  prettier_print
27
27
  temple (0.8.2)
28
28
  tilt (2.0.10)
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
+ require "syntax_tree/rake_tasks"
5
6
 
6
7
  Rake::TestTask.new(:test) do |t|
7
8
  t.libs << "test"
@@ -9,4 +10,10 @@ Rake::TestTask.new(:test) do |t|
9
10
  t.test_files = FileList["test/**/*_test.rb"]
10
11
  end
11
12
 
13
+ SOURCE_FILES =
14
+ FileList[%w[Gemfile Rakefile syntax_tree-haml.gemspec lib/**/*.rb test/*.rb]]
15
+
16
+ SyntaxTree::Rake::CheckTask.new { |t| t.source_files = SOURCE_FILES }
17
+ SyntaxTree::Rake::WriteTask.new { |t| t.source_files = SOURCE_FILES }
18
+
12
19
  task default: :test
@@ -0,0 +1,425 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module Haml
5
+ class Format < Visitor
6
+ attr_reader :q
7
+
8
+ def initialize(q)
9
+ @q = q
10
+ end
11
+
12
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#html-comments-
13
+ def visit_comment(node)
14
+ with_children(node) do
15
+ q.text("/")
16
+ q.text("!") if node.value[:revealed]
17
+
18
+ if node.value[:conditional]
19
+ q.text(node.value[:conditional])
20
+ elsif node.value[:text]
21
+ q.text(" #{node.value[:text]}")
22
+ end
23
+ end
24
+ end
25
+
26
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#doctype-
27
+ def visit_doctype(node)
28
+ parts = ["!!!"]
29
+
30
+ parts << if DOCTYPE_TYPES.key?(node.value[:type])
31
+ DOCTYPE_TYPES[node.value[:type]]
32
+ elsif DOCTYPE_VERSIONS.include?(node.value[:version])
33
+ node.value[:version]
34
+ else
35
+ node.value[:type]
36
+ end
37
+
38
+ parts << node.value[:encoding] if node.value[:encoding]
39
+ q.text(parts.join(" "))
40
+ end
41
+
42
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#filter
43
+ def visit_filter(node)
44
+ q.group do
45
+ q.text(":")
46
+ q.text(node.value[:name])
47
+
48
+ q.indent do
49
+ q.breakable(force: true)
50
+
51
+ segments = node.value[:text].strip.split("\n")
52
+ q.seplist(segments, -> { q.breakable(force: true) }) do |segment|
53
+ q.text(segment)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#haml-comments--
60
+ def visit_haml_comment(node)
61
+ q.text("-#")
62
+ text = node.value[:text].strip
63
+
64
+ if text.include?("\n")
65
+ q.indent do
66
+ q.breakable(force: true)
67
+ q.seplist(
68
+ text.split("\n"),
69
+ -> { q.breakable(force: true) }
70
+ ) { |segment| q.text(segment) }
71
+ end
72
+ else
73
+ q.text(" #{text}")
74
+ end
75
+ end
76
+
77
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#plain-text
78
+ def visit_plain(node)
79
+ text = node.value[:text]
80
+ q.text("\\") if escaped?(text)
81
+ q.text(text)
82
+ end
83
+
84
+ # Visit the root node of the AST.
85
+ def visit_root(node)
86
+ node.children.each do |child|
87
+ visit(child)
88
+ q.breakable(force: true)
89
+ end
90
+ end
91
+
92
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#inserting_ruby
93
+ def visit_script(node)
94
+ with_children(node) do
95
+ q.text("&") if node.value[:escape_html]
96
+
97
+ node.value[:preserve] ? q.text("~") : q.text("=")
98
+
99
+ q.text(" ")
100
+ q.text(node.value[:text].strip)
101
+ end
102
+ end
103
+
104
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#running-ruby--
105
+ def visit_silent_script(node)
106
+ q.group do
107
+ q.text("- ")
108
+ q.text(node.value[:text].strip)
109
+
110
+ node.children.each do |child|
111
+ if continuation?(node, child)
112
+ q.breakable(force: true)
113
+ visit(child)
114
+ else
115
+ q.indent do
116
+ q.breakable(force: true)
117
+ visit(child)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ LiteralHashValue = Struct.new(:value)
125
+
126
+ # When formatting a tag, there are a lot of different kinds of things that
127
+ # can be printed out. There's the tag name, the attributes, the content,
128
+ # etc. This object is responsible for housing all of those parts.
129
+ class PartList
130
+ attr_reader :node, :parts
131
+
132
+ def initialize(node)
133
+ @node = node
134
+ @parts = []
135
+ end
136
+
137
+ def <<(part)
138
+ parts << part
139
+ end
140
+
141
+ def empty?
142
+ parts.empty?
143
+ end
144
+
145
+ def format(q)
146
+ if empty? && node.value[:name] == "div"
147
+ # If we don't have any other parts to print and the tag is a div
148
+ # then we need to make sure to add that to the beginning. Otherwise
149
+ # it's implied by the presence of other operators.
150
+ q.text("%div")
151
+ else
152
+ parts.inject(0) do |align, part|
153
+ part.format(q, align)
154
+ align + part.length
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ class PlainPart < Struct.new(:value)
161
+ def format(q, align)
162
+ q.text(value)
163
+ end
164
+
165
+ def length
166
+ value.length
167
+ end
168
+ end
169
+
170
+ class PrefixPart < Struct.new(:prefix, :value)
171
+ def format(q, align)
172
+ q.text("#{prefix}#{value}")
173
+ end
174
+
175
+ def length
176
+ prefix.length + value.length
177
+ end
178
+ end
179
+
180
+ class HTMLAttributesPart
181
+ attr_reader :values
182
+
183
+ def initialize(raw)
184
+ @values =
185
+ raw[1...-1]
186
+ .split(",")
187
+ .to_h { |keypair| keypair[1..-1].split("\" => ") }
188
+ end
189
+
190
+ def format(q, align)
191
+ q.group do
192
+ q.text("(")
193
+ q.nest(align) do
194
+ q.seplist(
195
+ values,
196
+ -> { q.fill_breakable },
197
+ :each_pair
198
+ ) { |key, value| q.text("#{key}=#{value}") }
199
+ end
200
+ q.text(")")
201
+ end
202
+ end
203
+
204
+ def length
205
+ values.sum { |key, value| key.length + value.length + 3 }
206
+ end
207
+ end
208
+
209
+ class HashAttributesPart < Struct.new(:values)
210
+ def format(q, align)
211
+ format_value(q, values)
212
+ end
213
+
214
+ def length
215
+ values.sum do |key, value|
216
+ key.length + (value.is_a?(String) ? value : value.to_s).length + 3
217
+ end
218
+ end
219
+
220
+ private
221
+
222
+ def format_value(q, hash, level = 0)
223
+ q.group do
224
+ q.text("{")
225
+ q.indent do
226
+ q.group do
227
+ q.breakable(level == 0 ? "" : " ")
228
+ q.seplist(hash, nil, :each_pair) do |key, value|
229
+ q.text(Format.hash_key(key))
230
+ q.text(" ")
231
+
232
+ if value.is_a?(Hash)
233
+ format_value(q, value, level + 1)
234
+ else
235
+ q.text(Format.hash_value(value))
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ q.breakable(level == 0 ? "" : " ")
242
+ q.text("}")
243
+ end
244
+ end
245
+ end
246
+
247
+ def self.hash_key(key)
248
+ key.match?(/^@|[-:]/) ? "\"#{key}\":" : "#{key}:"
249
+ end
250
+
251
+ def self.hash_value(value)
252
+ case value
253
+ when LiteralHashValue
254
+ value.value
255
+ when String
256
+ "\"#{Quotes.normalize(value, "\"")}\""
257
+ else
258
+ value.to_s
259
+ end
260
+ end
261
+
262
+ # Visit a tag node.
263
+ def visit_tag(node)
264
+ parts = PartList.new(node)
265
+
266
+ # If we have a tag that isn't a div, then we need to print out that
267
+ # name of that tag first. If it is a div, first we'll check if there
268
+ # are any other things that would force us to print out the div
269
+ # explicitly, and otherwise we'll leave it off.
270
+ if node.value[:name] != "div"
271
+ parts << PrefixPart.new("%", node.value[:name])
272
+ end
273
+
274
+ # If we have a class attribute, then we're going to print that here
275
+ # using the special class syntax.
276
+ if node.value[:attributes].key?("class")
277
+ parts << PrefixPart.new(
278
+ ".",
279
+ node.value[:attributes]["class"].tr(" ", ".")
280
+ )
281
+ end
282
+
283
+ # If we have an id attribute, then we're going to print that here
284
+ # using the special id syntax.
285
+ if node.value[:attributes].key?("id")
286
+ parts << PrefixPart.new("#", node.value[:attributes]["id"])
287
+ end
288
+
289
+ # If we're using dynamic attributes on this tag, then they come in as
290
+ # a string that looks like the output of Hash#inspect from Ruby. So
291
+ # here we're going to split it all up and print it out nicely.
292
+ if node.value[:dynamic_attributes].new
293
+ parts << HTMLAttributesPart.new(node.value[:dynamic_attributes].new)
294
+ end
295
+
296
+ # If there are any static attributes that are not class or id (because
297
+ # we already took care of those), then we're going to print them out
298
+ # here.
299
+ static =
300
+ node.value[:attributes].reject do |key, _|
301
+ key == "class" || key == "id"
302
+ end
303
+
304
+ parts << HashAttributesPart.new(static) if static.any?
305
+
306
+ # If there are dynamic attributes that don't use the newer syntax, then
307
+ # we're going to print them out here.
308
+ if node.value[:dynamic_attributes].old
309
+ parts << PlainPart.new("%div") if parts.empty?
310
+
311
+ if ::Haml::AttributeParser.available?
312
+ dynamic = parse_attributes(node.value[:dynamic_attributes].old)
313
+ parts << if dynamic.is_a?(LiteralHashValue)
314
+ PlainPart.new(dynamic.value)
315
+ else
316
+ HashAttributesPart.new(dynamic)
317
+ end
318
+ else
319
+ parts << PlainPart.new(node.value[:dynamic_attributes].old)
320
+ end
321
+ end
322
+
323
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#object-reference-
324
+ if node.value[:object_ref] != :nil
325
+ parts << PlainPart.new("%div") if parts.empty?
326
+ parts << PlainPart.new(node.value[:object_ref])
327
+ end
328
+
329
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#whitespace-removal--and-
330
+ parts << PlainPart.new(">") if node.value[:nuke_outer_whitespace]
331
+ parts << PlainPart.new("<") if node.value[:nuke_inner_whitespace]
332
+
333
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#empty-void-tags-
334
+ parts << PlainPart.new("/") if node.value[:self_closing]
335
+
336
+ # If there is a value part, then we're going to print slightly
337
+ # differently as the value goes after the tag declaration.
338
+ if (value = node.value[:value]) && !value.empty?
339
+ with_children(node) do
340
+ q.group { parts.format(q) }
341
+ q.indent do
342
+ # Split between the declaration of the tag and the contents of the
343
+ # tag.
344
+ q.breakable("")
345
+
346
+ if node.value[:parse] && value.match?(/#[{$@]/)
347
+ # There's a weird case here where if the value includes
348
+ # interpolation and it's marked as { parse: true }, then we
349
+ # don't actually want the = prefix, and we want to remove extra
350
+ # escaping.
351
+ q.if_break { q.text("") }.if_flat { q.text(" ") }
352
+ q.text(value[1...-1].gsub(/\\"/, "\""))
353
+ elsif node.value[:parse]
354
+ q.text("= ")
355
+ q.text(value)
356
+ else
357
+ q.if_break { q.text("") }.if_flat { q.text(" ") }
358
+ q.text(value)
359
+ end
360
+ end
361
+ end
362
+ else
363
+ with_children(node) { parts.format(q) }
364
+ end
365
+ end
366
+
367
+ private
368
+
369
+ # When printing out sequences of silent scripts, sometimes subsequent nodes
370
+ # will be continuations of previous nodes. In that case we want to dedent
371
+ # them to match.
372
+ def continuation?(node, child)
373
+ return false if child.type != :silent_script
374
+
375
+ case [node.value[:keyword], child.value[:keyword]]
376
+ in ["case", "in" | "when" | "else"]
377
+ true
378
+ in ["if" | "unless", "elsif" | "else"]
379
+ true
380
+ else
381
+ false
382
+ end
383
+ end
384
+
385
+ # If a node comes in as the plain type but starts with one of the special
386
+ # characters that haml parses, then we need to escape it with a \ when
387
+ # printing.
388
+ def escaped?(text)
389
+ ::Haml::Parser::SPECIAL_CHARACTERS.any? do |special|
390
+ text.start_with?(special)
391
+ end
392
+ end
393
+
394
+ # Take a source string and attempt to parse it into a set of attributes that
395
+ # can be used to format the source.
396
+ def parse_attributes(source)
397
+ case Ripper.sexp(source)
398
+ in [:program, [[:hash, *], *]] if parsed =
399
+ ::Haml::AttributeParser.parse(source)
400
+ parsed.to_h { |key, value| [key, parse_attributes(value)] }
401
+ in [:program, [[:string_literal, *], *]]
402
+ source[1...-1]
403
+ else
404
+ LiteralHashValue.new(source)
405
+ end
406
+ end
407
+
408
+ def with_children(node)
409
+ if node.children.empty?
410
+ q.group { yield }
411
+ else
412
+ q.group do
413
+ q.group { yield }
414
+ q.indent do
415
+ node.children.each do |child|
416
+ q.breakable(force: true)
417
+ visit(child)
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end
425
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module Haml
5
+ class PrettyPrint < Visitor
6
+ attr_reader :q
7
+
8
+ def initialize(q)
9
+ @q = q
10
+ end
11
+
12
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#html-comments-
13
+ def visit_comment(node)
14
+ group("comment") do
15
+ if node.value[:conditional]
16
+ pp_field("conditional", node.value[:conditional])
17
+ elsif node.value[:text]
18
+ pp_field("text", node.value[:text])
19
+ end
20
+
21
+ bool_field("revealed") if node.value[:revealed]
22
+ pp_field("children", node.children) if node.children.any?
23
+ end
24
+ end
25
+
26
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#doctype-
27
+ def visit_doctype(node)
28
+ group("doctype") do
29
+ if DOCTYPE_TYPES.key?(node.value[:type])
30
+ pp_field("type", node.value[:type])
31
+ elsif DOCTYPE_VERSIONS.include?(node.value[:version])
32
+ pp_field("version", node.value[:version])
33
+ else
34
+ pp_field("text", node.value[:text])
35
+ end
36
+
37
+ pp_field("encoding", node.value[:encoding]) if node.value[:encoding]
38
+ end
39
+ end
40
+
41
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#filter
42
+ def visit_filter(node)
43
+ group("filter") do
44
+ text_field("name", node.value[:name])
45
+ pp_field("text", node.value[:text])
46
+ end
47
+ end
48
+
49
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#haml-comments--
50
+ def visit_haml_comment(node)
51
+ group("haml_comment") { pp_field("text", node.value[:text]) }
52
+ end
53
+
54
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#plain-text
55
+ def visit_plain(node)
56
+ group("plain") { pp_field("text", node.value[:text]) }
57
+ end
58
+
59
+ # Visit the root node of the AST.
60
+ def visit_root(node)
61
+ group("root") do
62
+ pp_field("children", node.children) if node.children.any?
63
+ end
64
+ end
65
+
66
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#inserting_ruby
67
+ def visit_script(node)
68
+ group("script") do
69
+ pp_field("text", node.value[:text])
70
+ bool_field("escape_html") if node.value[:escape_html]
71
+ bool_field("preserve") if node.value[:preserve]
72
+ pp_field("children", node.children) if node.children.any?
73
+ end
74
+ end
75
+
76
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#running-ruby--
77
+ def visit_silent_script(node)
78
+ group("silent-script") do
79
+ pp_field("text", node.value[:text])
80
+ pp_field("children", node.children) if node.children.any?
81
+ end
82
+ end
83
+
84
+ # Visit a tag node.
85
+ def visit_tag(node)
86
+ group("tag") do
87
+ pp_field("name", node.value[:name])
88
+
89
+ if node.value[:attributes].any?
90
+ pp_field("attributes", node.value[:attributes])
91
+ end
92
+
93
+ if node.value[:dynamic_attributes].new
94
+ pp_field(
95
+ "dynamic_attributes.new",
96
+ node.value[:dynamic_attributes].new
97
+ )
98
+ end
99
+
100
+ if node.value[:dynamic_attributes].old
101
+ pp_field(
102
+ "dynamic_attributes.old",
103
+ node.value[:dynamic_attributes].old
104
+ )
105
+ end
106
+
107
+ if node.value[:object_ref] != :nil
108
+ pp_field("object_ref", node.value[:object_ref])
109
+ end
110
+
111
+ if node.value[:nuke_outer_whitespace]
112
+ bool_field("nuke_outer_whitespace")
113
+ end
114
+
115
+ if node.value[:nuke_inner_whitespace]
116
+ bool_field("nuke_inner_whitespace")
117
+ end
118
+
119
+ bool_field("self_closing") if node.value[:self_closing]
120
+ pp_field("value", node.value[:value]) if node.value[:value]
121
+ pp_field("children", node.children) if node.children.any?
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def bool_field(name)
128
+ q.breakable
129
+ q.text(name)
130
+ end
131
+
132
+ def group(name)
133
+ q.group do
134
+ q.text("(")
135
+ q.text(name)
136
+
137
+ q.nest(2) { yield }
138
+ q.breakable("")
139
+ q.text(")")
140
+ end
141
+ end
142
+
143
+ def pp_field(name, value)
144
+ q.breakable
145
+ q.text(name)
146
+ q.text("=")
147
+ q.pp(value)
148
+ end
149
+
150
+ def text_field(name, value)
151
+ q.breakable
152
+ q.text(name)
153
+ q.text("=")
154
+ q.text(value)
155
+ end
156
+ end
157
+ end
158
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SyntaxTree
4
4
  module Haml
5
- VERSION = "1.2.0"
5
+ VERSION = "1.2.1"
6
6
  end
7
7
  end