syntax_tree-haml 1.0.1 → 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: 92743881ec6d45fc9351e4feb1121e630bd10ac8c9f719b15c5a02b9271c3d59
4
- data.tar.gz: 2cdab254a71ec97c4c356fcd9b16377c9e7c07648e7dce5aa99182fbe5e53826
3
+ metadata.gz: 3ec66452fbab00bf664cbd1d86b9b351a039eb99ac70547dc2defd72003122da
4
+ data.tar.gz: 5e643711d3d79a8e7c37bc6f17afbb9d855eab2fbf7992f348014cd14ead9490
5
5
  SHA512:
6
- metadata.gz: f802bec379fe6834229c178ff0e419b3498f66341b60c8d8c949f617fa19180433523f36f8aedca9788a76b6dbce99d572894af6f9ec0fd1583494b86393310b
7
- data.tar.gz: cdb61f50440567ef2eeecdfd4edc41fba68443bc62c46b9c51e1c29569fe312e60142e9123f6676f9a0dfbf2248b1e664ac4afc800e077dbe526e918b8c89189
6
+ metadata.gz: 6a55f022617f25d7898f92ebdcca872cd3920e2177144dc63caa117a8a12bf4c15ee9ec9ad5dedd65312b64191fdf40725b6e83bf1733066e6055901bc374699
7
+ data.tar.gz: b06d3310d759f3b11fda480172d66253edc1843740f59aef360a7ca5e7d2130d171ee27d272e73a52038ac50b7f1345dcb0a8a4c89a85c3f4a98914a73aee1c9
@@ -4,6 +4,13 @@ on:
4
4
  - pull_request_target
5
5
  jobs:
6
6
  ci:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ ruby:
11
+ - '2.7'
12
+ - '3.0'
13
+ - '3.1'
7
14
  name: CI
8
15
  runs-on: ubuntu-latest
9
16
  env:
@@ -13,9 +20,11 @@ jobs:
13
20
  - uses: ruby/setup-ruby@v1
14
21
  with:
15
22
  bundler-cache: true
16
- ruby-version: '3.1'
23
+ ruby-version: ${{ matrix.ruby }}
17
24
  - name: Test
18
- run: bundle exec rake test
25
+ run: |
26
+ bundle exec rake test
27
+ bundle exec rake stree:check
19
28
  automerge:
20
29
  name: AutoMerge
21
30
  needs: ci
data/CHANGELOG.md CHANGED
@@ -6,6 +6,24 @@ 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
+
15
+ ## [1.2.0] - 2022-05-13
16
+
17
+ ### Added
18
+
19
+ - An optional `maxwidth` second argument to `SyntaxTree::Haml.format`.
20
+
21
+ ## [1.1.0] - 2022-04-22
22
+
23
+ ### Added
24
+
25
+ - Support for Ruby 2.7 added back.
26
+
9
27
  ## [1.0.1] - 2022-03-31
10
28
 
11
29
  ### Changed
@@ -24,7 +42,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
24
42
 
25
43
  - 🎉 Initial release! 🎉
26
44
 
27
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.0.1...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
47
+ [1.2.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.1.0...v1.2.0
48
+ [1.1.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.0.1...v1.1.0
28
49
  [1.0.1]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.0.0...v1.0.1
29
50
  [1.0.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v0.1.0...v1.0.0
30
51
  [0.1.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/c1264c...v0.1.0
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree-haml (1.0.1)
4
+ syntax_tree-haml (1.2.1)
5
5
  haml (>= 5.2)
6
+ prettier_print
6
7
  syntax_tree (>= 2.0.1)
7
8
 
8
9
  GEM
@@ -12,7 +13,8 @@ GEM
12
13
  haml (5.2.2)
13
14
  temple (>= 0.8.0)
14
15
  tilt
15
- minitest (5.15.0)
16
+ minitest (5.16.2)
17
+ prettier_print (0.1.0)
16
18
  rake (13.0.6)
17
19
  simplecov (0.21.2)
18
20
  docile (~> 1.1)
@@ -20,11 +22,15 @@ GEM
20
22
  simplecov_json_formatter (~> 0.1)
21
23
  simplecov-html (0.12.3)
22
24
  simplecov_json_formatter (0.1.4)
23
- syntax_tree (2.0.1)
25
+ syntax_tree (3.2.0)
26
+ prettier_print
24
27
  temple (0.8.2)
25
28
  tilt (2.0.10)
26
29
 
27
30
  PLATFORMS
31
+ ruby
32
+ x86_64-darwin-19
33
+ x86_64-darwin-20
28
34
  x86_64-darwin-21
29
35
  x86_64-linux
30
36
 
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