syntax_tree-haml 1.2.0 → 1.2.1

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: 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