w_syntax_tree-erb 0.9.5 → 0.10.0

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: 7625d38e4764dc486d61c0141a4e25892efa6fc993779b08885110dd55f830e6
4
- data.tar.gz: 1551342a3b0550f0e59bf92c184586f0b8ea401bb2c4f55058e6fb1b7d0759f7
3
+ metadata.gz: e48aeaba8ef070f5f13bdd166cf4dd9f1f1e4693de8537d2dbf5b86f90092de6
4
+ data.tar.gz: ade025cc4a31c3f0b5461650a97ff2074de6dc868197ab09a2d2b0c047b1d0cc
5
5
  SHA512:
6
- metadata.gz: c737cc77acef8c790b3e08a99aacc509e01e69ea67a70ecfa2684db9034bcb65c4ec7cb705fafe6e8c147a8f0a4237d015ebeedddd213e26c7a75fe123348618
7
- data.tar.gz: 38f27bdf74980a3fae073b2fc5ff5e2114eacfe69886da0b819e62ac5547ae220e369de722a0284aa556dfce2f1e00e3b61364838e43438961119891fdf4f2b8
6
+ metadata.gz: 58ff254dc23a076b7bbc87606c3672c8bde4b5df6d526cb6ea5d6cedf54c1f9fb9b0b832b7ae06d7eb23731bf5a95cade7d0a443e9ce11ea71a1578ce0591d49
7
+ data.tar.gz: 30fbe095d0e624ed126897ff1fccc229a03f4a0375d67e9718257900856e1587be7b406a510b61e8ea6574af9c7a1c46fd2ee0c15586819c20e90e452bfbbace
data/.husky/pre-commit ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ npx lint-staged
@@ -0,0 +1,25 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "ruby_lsp",
9
+ "name": "Debug file",
10
+ "request": "launch",
11
+ "program": "ruby ${file}"
12
+ },
13
+ {
14
+ "type": "ruby_lsp",
15
+ "name": "Debug test",
16
+ "request": "launch",
17
+ "program": "ruby -Itest ${relativeFile}"
18
+ },
19
+ {
20
+ "type": "ruby_lsp",
21
+ "name": "Debug attach",
22
+ "request": "attach"
23
+ }
24
+ ]
25
+ }
data/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.0] - 2023-08-20
10
+
11
+ - Changes how whitespace and newlines are handled.
12
+ - Supports syntax like:
13
+
14
+ ```erb
15
+ <%= part %> / <%= total %> (<%= percentage %>%)
16
+ ```
17
+
9
18
  ## [0.9.5] - 2023-07-02
10
19
 
11
20
  - Fixes ruby comment in ERB-tag included VoidStatement
@@ -61,5 +70,11 @@ Output:
61
70
  - Can format a lot of .html.erb-syntax and works as a plugin to syntax_tree.
62
71
  - This is still early and there are a lot of different weird syntaxes out there.
63
72
 
64
- [unreleased]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.0...HEAD
73
+ [unreleased]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.10.0...HEAD
74
+ [0.10.0]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.5...v0.10.0
75
+ [0.9.5]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.4...v0.9.5
76
+ [0.9.4]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.3...v0.9.4
77
+ [0.9.3]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.2...v0.9.3
78
+ [0.9.2]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.1...v0.9.2
79
+ [0.9.1]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.0...v0.9.1
65
80
  [0.9.0]: https://github.com/davidwessman/syntax_tree-erb/compare/419727a73af94057ca0980733e69ac8b4d52fdf4...v0.9.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- w_syntax_tree-erb (0.9.5)
4
+ w_syntax_tree-erb (0.10.0)
5
5
  prettier_print (~> 1.2, >= 1.2.0)
6
6
  syntax_tree (~> 6.1, >= 6.1.1)
7
7
 
@@ -9,7 +9,7 @@ GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
11
  docile (1.4.0)
12
- minitest (5.18.1)
12
+ minitest (5.19.0)
13
13
  prettier_print (1.2.1)
14
14
  rake (13.0.6)
15
15
  simplecov (0.22.0)
data/README.md CHANGED
@@ -30,7 +30,7 @@ Currently handles
30
30
  Add this line to your application's Gemfile:
31
31
 
32
32
  ```ruby
33
- gem "w_syntax_tree-erb", "~> 0.9", require: false
33
+ gem "w_syntax_tree-erb", "~> 0.10", require: false
34
34
  ```
35
35
 
36
36
  > I added the `w_` prefix to avoid conflicts if there will ever be an official `syntax_tree-erb` gem.
@@ -77,6 +77,21 @@ Dir
77
77
  puts failures
78
78
  ```
79
79
 
80
+ ## Development
81
+
82
+ Setup linting:
83
+
84
+ ```sh
85
+ npm run prepare
86
+ ```
87
+
88
+ Install dependencies and run tests:
89
+
90
+ ```sh
91
+ bundle
92
+ bundle exec rake
93
+ ```
94
+
80
95
  ## Contributing
81
96
 
82
97
  Bug reports and pull requests are welcome on GitHub at https://github.com/davidwessman/syntax_tree-erb.
@@ -22,9 +22,7 @@ module SyntaxTree
22
22
  def visit_document(node)
23
23
  child_nodes = node.child_nodes.sort_by(&:location)
24
24
 
25
- q.seplist(child_nodes, -> { q.breakable(force: true) }) do |child_node|
26
- visit(child_node)
27
- end
25
+ handle_child_nodes(child_nodes)
28
26
 
29
27
  q.breakable(force: true)
30
28
  end
@@ -32,31 +30,55 @@ module SyntaxTree
32
30
  def visit_block(node)
33
31
  visit(node.opening)
34
32
 
33
+ breakable = breakable_inside(node)
35
34
  if node.elements.any?
36
35
  q.indent do
37
- q.breakable("")
38
- q.seplist(
39
- node.elements,
40
- -> { q.breakable(force: true) }
41
- ) { |child_node| visit(child_node) }
36
+ q.breakable if breakable
37
+ handle_child_nodes(node.elements)
42
38
  end
43
39
  end
44
40
 
45
41
  if node.closing
46
- q.breakable("")
42
+ q.breakable("") if breakable
47
43
  visit(node.closing)
48
44
  end
49
45
  end
50
46
 
51
- def visit_html(node)
52
- # Make sure to group the tags together if there is no child nodes.
47
+ def visit_html_groupable(node, group)
53
48
  if node.elements.size == 0
54
- q.group do
55
- visit(node.opening)
56
- visit(node.closing)
49
+ visit(node.opening)
50
+ visit(node.closing)
51
+ else
52
+ visit(node.opening)
53
+
54
+ with_break = breakable_inside(node)
55
+ q.indent do
56
+ if with_break
57
+ group ? q.breakable("") : q.breakable
58
+ end
59
+ handle_child_nodes(node.elements)
60
+ end
61
+
62
+ if with_break
63
+ group ? q.breakable("") : q.breakable
57
64
  end
65
+ visit(node.closing)
66
+ end
67
+ end
68
+
69
+ def visit_html(node)
70
+ # Make sure to group the tags together if there is no child nodes.
71
+ if node.elements.size == 0 ||
72
+ node.elements.any? { |node|
73
+ node.is_a?(SyntaxTree::ERB::CharData)
74
+ } ||
75
+ (
76
+ node.elements.size == 1 &&
77
+ node.elements.first.is_a?(SyntaxTree::ERB::ErbNode)
78
+ )
79
+ q.group { visit_html_groupable(node, true) }
58
80
  else
59
- visit_block(node)
81
+ visit_html_groupable(node, false)
60
82
  end
61
83
  end
62
84
 
@@ -146,7 +168,7 @@ module SyntaxTree
146
168
  formatter.flush
147
169
  rows = formatter.output.join.split("\n")
148
170
 
149
- output_rows(formatter.output.join.split("\n"))
171
+ output_rows(rows)
150
172
  end
151
173
 
152
174
  def output_rows(rows)
@@ -170,9 +192,14 @@ module SyntaxTree
170
192
  visit(child_node)
171
193
  end
172
194
  end
195
+
196
+ # Only add breakable if we have attributes
197
+ q.breakable(node.closing.value == "/>" ? " " : "")
198
+ elsif node.closing.value == "/>"
199
+ # Need a space before end-tag for self-closing
200
+ q.text(" ")
173
201
  end
174
202
 
175
- q.breakable(node.closing.value == "/>" ? " " : "")
176
203
  visit(node.closing)
177
204
  end
178
205
  end
@@ -208,15 +235,20 @@ module SyntaxTree
208
235
  visit(node.token)
209
236
  end
210
237
 
238
+ def visit_erb_comment(node)
239
+ visit(node.token)
240
+ end
241
+
211
242
  # Visit a CharData node.
212
243
  def visit_char_data(node)
213
- lines = node.value.value.strip.split("\n")
244
+ return if node.value.value.strip.empty?
214
245
 
215
- if lines.size > 0
216
- q.seplist(lines, -> { q.breakable(indent: false) }) do |line|
217
- q.text(line)
218
- end
219
- end
246
+ q.text(node.value.value)
247
+ end
248
+
249
+ def visit_new_line(node)
250
+ q.breakable(force: :skip_parent_break)
251
+ q.breakable(force: :skip_parent_break) if node.count > 1
220
252
  end
221
253
 
222
254
  # Visit a Doctype node.
@@ -230,6 +262,49 @@ module SyntaxTree
230
262
  end
231
263
  end
232
264
 
265
+ private
266
+
267
+ def breakable_inside(node)
268
+ if node.is_a?(SyntaxTree::ERB::HtmlNode)
269
+ node.elements.first.class != SyntaxTree::ERB::CharData ||
270
+ node_new_line_count(node.opening) > 0
271
+ elsif node.is_a?(SyntaxTree::ERB::Block)
272
+ true
273
+ end
274
+ end
275
+
276
+ def breakable_between(node, next_node)
277
+ new_lines = node_new_line_count(node)
278
+
279
+ if new_lines == 1
280
+ q.breakable
281
+ elsif new_lines > 1
282
+ q.breakable
283
+ q.breakable(force: :skip_parent_break)
284
+ elsif next_node && !node.is_a?(SyntaxTree::ERB::CharData) &&
285
+ !next_node.is_a?(SyntaxTree::ERB::CharData)
286
+ q.breakable
287
+ end
288
+ end
289
+
290
+ def breakable_between_group(node, next_node)
291
+ new_lines = node_new_line_count(node)
292
+
293
+ if new_lines == 1
294
+ q.breakable(force: true)
295
+ elsif new_lines > 1
296
+ q.breakable(force: true)
297
+ q.breakable(force: true)
298
+ elsif next_node && !node.is_a?(SyntaxTree::ERB::CharData) &&
299
+ !next_node.is_a?(SyntaxTree::ERB::CharData)
300
+ q.breakable("")
301
+ end
302
+ end
303
+
304
+ def node_new_line_count(node)
305
+ node.respond_to?(:new_line) ? node.new_line&.count || 0 : 0
306
+ end
307
+
233
308
  def erb_print_width(node)
234
309
  # Set the width to maximum if we have an IfNode or IfOp,
235
310
  # we cannot format them purely with SyntaxTree because the ERB-syntax will be unparseable.
@@ -247,6 +322,60 @@ module SyntaxTree
247
322
  check_for_if_statement(child_node)
248
323
  end
249
324
  end
325
+
326
+ def handle_child_nodes(child_nodes)
327
+ group = []
328
+
329
+ if child_nodes.size == 1
330
+ visit(child_nodes.first)
331
+ return
332
+ end
333
+
334
+ child_nodes.each_with_index do |child_node, index|
335
+ is_last = index == child_nodes.size - 1
336
+
337
+ # Last element should not have new lines
338
+ node = is_last ? child_node.without_new_line : child_node
339
+
340
+ if node_should_group(node)
341
+ group << node
342
+ next
343
+ end
344
+
345
+ # Render all group elements before the current node
346
+ handle_group(group, break_after: true)
347
+ group = []
348
+
349
+ # Render the current node
350
+ visit(node)
351
+ next_node = child_nodes[index + 1]
352
+
353
+ breakable_between(node, next_node)
354
+ end
355
+
356
+ # Handle group if we have any nodes left
357
+ handle_group(group, break_after: false)
358
+ end
359
+
360
+ def handle_group(nodes, break_after:)
361
+ return unless nodes.any?
362
+
363
+ q.group do
364
+ nodes.each_with_index do |node, group_index|
365
+ visit(node)
366
+ next_node = nodes[group_index + 1]
367
+ next if next_node.nil?
368
+ breakable_between_group(node, next_node)
369
+ end
370
+ end
371
+
372
+ breakable_between_group(nodes.last, nil) if break_after
373
+ end
374
+
375
+ def node_should_group(node)
376
+ node.is_a?(SyntaxTree::ERB::CharData) ||
377
+ node.is_a?(SyntaxTree::ERB::ErbNode)
378
+ end
250
379
  end
251
380
  end
252
381
  end
@@ -53,6 +53,14 @@ module SyntaxTree
53
53
  def pretty_print(q)
54
54
  PrettyPrint.new(q).visit(self)
55
55
  end
56
+
57
+ def without_new_line
58
+ self
59
+ end
60
+
61
+ def skip?
62
+ false
63
+ end
56
64
  end
57
65
 
58
66
  # A Token is any kind of lexical token from the source. It has a type, a
@@ -110,6 +118,25 @@ module SyntaxTree
110
118
  end
111
119
  end
112
120
 
121
+ # This is a base class for a Node that can also hold an appended
122
+ # new line.
123
+ class Element < Node
124
+ attr_reader(:new_line, :location)
125
+
126
+ def initialize(new_line:, location:)
127
+ @new_line = new_line
128
+ @location = location
129
+ end
130
+
131
+ def without_new_line
132
+ self.class.new(**deconstruct_keys([]).merge(new_line: nil))
133
+ end
134
+
135
+ def deconstruct_keys(keys)
136
+ { new_line: new_line, location: location }
137
+ end
138
+ end
139
+
113
140
  # This is a base class for a block that contains:
114
141
  # - an opening
115
142
  # - optional elements
@@ -131,12 +158,22 @@ module SyntaxTree
131
158
  [opening, *elements, closing].compact
132
159
  end
133
160
 
161
+ def new_line
162
+ closing.new_line if closing.respond_to?(:new_line)
163
+ end
164
+
165
+ def without_new_line
166
+ self.class.new(
167
+ **deconstruct_keys([]).merge(closing: closing&.without_new_line)
168
+ )
169
+ end
170
+
134
171
  alias deconstruct child_nodes
135
172
 
136
173
  def deconstruct_keys(keys)
137
174
  {
138
175
  opening: opening,
139
- content: content,
176
+ elements: elements,
140
177
  closing: closing,
141
178
  location: location
142
179
  }
@@ -151,15 +188,22 @@ module SyntaxTree
151
188
  # The opening tag of an element. It contains the opening character (<),
152
189
  # the name of the element, any optional attributes, and the closing
153
190
  # token (either > or />).
154
- class OpeningTag < Node
155
- attr_reader :opening, :name, :attributes, :closing, :location
156
-
157
- def initialize(opening:, name:, attributes:, closing:, location:)
191
+ class OpeningTag < Element
192
+ attr_reader :opening, :name, :attributes, :closing
193
+
194
+ def initialize(
195
+ opening:,
196
+ name:,
197
+ attributes:,
198
+ closing:,
199
+ new_line:,
200
+ location:
201
+ )
202
+ super(new_line: new_line, location: location)
158
203
  @opening = opening
159
204
  @name = name
160
205
  @attributes = attributes
161
206
  @closing = closing
162
- @location = location
163
207
  end
164
208
 
165
209
  def accept(visitor)
@@ -173,26 +217,25 @@ module SyntaxTree
173
217
  alias deconstruct child_nodes
174
218
 
175
219
  def deconstruct_keys(keys)
176
- {
220
+ super.merge(
177
221
  opening: opening,
178
222
  name: name,
179
223
  attributes: attributes,
180
- closing: closing,
181
- location: location
182
- }
224
+ closing: closing
225
+ )
183
226
  end
184
227
  end
185
228
 
186
229
  # The closing tag of an element. It contains the opening character (<),
187
230
  # the name of the element, and the closing character (>).
188
- class ClosingTag < Node
189
- attr_reader :opening, :name, :closing, :location
231
+ class ClosingTag < Element
232
+ attr_reader :opening, :name, :closing
190
233
 
191
- def initialize(opening:, name:, closing:, location:)
234
+ def initialize(opening:, name:, closing:, location:, new_line:)
235
+ super(new_line: new_line, location: location)
192
236
  @opening = opening
193
237
  @name = name
194
238
  @closing = closing
195
- @location = location
196
239
  end
197
240
 
198
241
  def accept(visitor)
@@ -206,19 +249,41 @@ module SyntaxTree
206
249
  alias deconstruct child_nodes
207
250
 
208
251
  def deconstruct_keys(keys)
209
- { opening: opening, name: name, closing: closing, location: location }
252
+ super.merge(opening: opening, name: name, closing: closing)
210
253
  end
211
254
  end
212
255
 
256
+ def without_new_line
257
+ self.class.new(
258
+ **deconstruct_keys([]).merge(
259
+ opening: closing.nil? ? opening.without_new_line : opening,
260
+ closing: closing&.without_new_line
261
+ )
262
+ )
263
+ end
264
+
265
+ # The HTML-closing tag is responsible for new lines after the node.
266
+ def new_line
267
+ closing.nil? ? opening.new_line : closing&.new_line
268
+ end
269
+
213
270
  def accept(visitor)
214
271
  visitor.visit_html(self)
215
272
  end
216
273
  end
217
274
 
218
- class ErbNode < Node
219
- attr_reader :opening_tag, :keyword, :content, :closing_tag, :location
220
-
221
- def initialize(opening_tag:, keyword:, content:, closing_tag:, location:)
275
+ class ErbNode < Element
276
+ attr_reader :opening_tag, :keyword, :content, :closing_tag
277
+
278
+ def initialize(
279
+ opening_tag:,
280
+ keyword:,
281
+ content:,
282
+ closing_tag:,
283
+ new_line:,
284
+ location:
285
+ )
286
+ super(new_line: new_line, location: location)
222
287
  @opening_tag = opening_tag
223
288
  # prune whitespace from keyword
224
289
  @keyword =
@@ -229,12 +294,18 @@ module SyntaxTree
229
294
  location: keyword.location
230
295
  )
231
296
  end
232
- # Set content to nil if it is empty
233
- content ||= []
234
- content = content.map(&:value).join if content.is_a?(Array)
235
- @content = ErbContent.new(value: content) unless content.strip.empty?
297
+
298
+ @content =
299
+ if content.is_a?(ErbContent)
300
+ content
301
+ else
302
+ # Set content to nil if it is empty
303
+ content ||= []
304
+ content = content.map(&:value).join if content.is_a?(Array)
305
+ ErbContent.new(value: content) unless content.strip.empty?
306
+ end
307
+
236
308
  @closing_tag = closing_tag
237
- @location = location
238
309
  end
239
310
 
240
311
  def accept(visitor)
@@ -245,30 +316,50 @@ module SyntaxTree
245
316
  [opening_tag, keyword, content, closing_tag].compact
246
317
  end
247
318
 
319
+ def new_line
320
+ closing_tag&.new_line
321
+ end
322
+
323
+ def without_new_line
324
+ self.class.new(
325
+ **deconstruct_keys([]).merge(
326
+ closing_tag: closing_tag.without_new_line
327
+ )
328
+ )
329
+ end
330
+
248
331
  alias deconstruct child_nodes
249
332
 
250
333
  def deconstruct_keys(keys)
251
- {
334
+ super.merge(
252
335
  opening_tag: opening_tag,
253
336
  keyword: keyword,
254
337
  content: content,
255
- closing_tag: closing_tag,
256
- location: location
257
- }
338
+ closing_tag: closing_tag
339
+ )
258
340
  end
259
341
  end
260
342
 
261
343
  class ErbBlock < Block
344
+ def initialize(opening:, location:, elements: nil, closing: nil)
345
+ super(
346
+ opening: opening,
347
+ location: location,
348
+ elements: elements,
349
+ closing: closing
350
+ )
351
+ end
352
+
262
353
  def accept(visitor)
263
354
  visitor.visit_erb_block(self)
264
355
  end
265
356
  end
266
357
 
267
- class ErbClose < Node
268
- attr_reader :location, :closing
358
+ class ErbClose < Element
359
+ attr_reader :closing
269
360
 
270
- def initialize(location:, closing:)
271
- @location = location
361
+ def initialize(closing:, new_line:, location:)
362
+ super(new_line: new_line, location: location)
272
363
  @closing = closing
273
364
  end
274
365
 
@@ -283,7 +374,7 @@ module SyntaxTree
283
374
  alias deconstruct child_nodes
284
375
 
285
376
  def deconstruct_keys(keys)
286
- { location: location, closing: closing }
377
+ super.merge(closing: closing)
287
378
  end
288
379
  end
289
380
 
@@ -442,12 +533,12 @@ module SyntaxTree
442
533
  end
443
534
  end
444
535
 
445
- class HtmlComment < Node
446
- attr_reader :token, :location
536
+ class HtmlComment < Element
537
+ attr_reader :token
447
538
 
448
- def initialize(token:, location:)
539
+ def initialize(token:, new_line:, location:)
540
+ super(new_line: new_line, location: location)
449
541
  @token = token
450
- @location = location
451
542
  end
452
543
 
453
544
  def accept(visitor)
@@ -461,18 +552,41 @@ module SyntaxTree
461
552
  alias deconstruct child_nodes
462
553
 
463
554
  def deconstruct_keys(keys)
464
- { token: token, location: location }
555
+ super.merge(token: token)
556
+ end
557
+ end
558
+
559
+ class ErbComment < Element
560
+ attr_reader :token
561
+
562
+ def initialize(token:, new_line:, location:)
563
+ super(new_line: new_line, location: location)
564
+ @token = token
565
+ end
566
+
567
+ def accept(visitor)
568
+ visitor.visit_erb_comment(self)
569
+ end
570
+
571
+ def child_nodes
572
+ []
573
+ end
574
+
575
+ alias deconstruct child_nodes
576
+
577
+ def deconstruct_keys(keys)
578
+ super.merge(token: token)
465
579
  end
466
580
  end
467
581
 
468
582
  # A CharData contains either plain text or whitespace within an element.
469
583
  # It wraps a single token value.
470
- class CharData < Node
471
- attr_reader :value, :location
584
+ class CharData < Element
585
+ attr_reader :value
472
586
 
473
- def initialize(value:, location:)
587
+ def initialize(value:, new_line:, location:)
588
+ super(new_line: new_line, location: location)
474
589
  @value = value
475
- @location = location
476
590
  end
477
591
 
478
592
  def accept(visitor)
@@ -486,7 +600,34 @@ module SyntaxTree
486
600
  alias deconstruct child_nodes
487
601
 
488
602
  def deconstruct_keys(keys)
489
- { value: value, location: location }
603
+ super.merge(value: value)
604
+ end
605
+
606
+ def skip?
607
+ value.value.strip.empty?
608
+ end
609
+ end
610
+
611
+ class NewLine < Node
612
+ attr_reader :count, :location
613
+
614
+ def initialize(location:, count:)
615
+ @location = location
616
+ @count = count
617
+ end
618
+
619
+ def accept(visitor)
620
+ visitor.visit_new_line(self)
621
+ end
622
+
623
+ def child_nodes
624
+ []
625
+ end
626
+
627
+ alias deconstruct child_nodes
628
+
629
+ def deconstruct_keys(keys)
630
+ { location: location, count: count }
490
631
  end
491
632
  end
492
633
 
@@ -494,14 +635,14 @@ module SyntaxTree
494
635
  # type of the document. It contains an opening declaration, the name of
495
636
  # the document type, an optional external identifier, and a closing of the
496
637
  # tag.
497
- class Doctype < Node
498
- attr_reader :opening, :name, :closing, :location
638
+ class Doctype < Element
639
+ attr_reader :opening, :name, :closing
499
640
 
500
- def initialize(opening:, name:, closing:, location:)
641
+ def initialize(opening:, name:, closing:, new_line:, location:)
642
+ super(new_line: new_line, location: location)
501
643
  @opening = opening
502
644
  @name = name
503
645
  @closing = closing
504
- @location = location
505
646
  end
506
647
 
507
648
  def accept(visitor)
@@ -515,7 +656,7 @@ module SyntaxTree
515
656
  alias deconstruct child_nodes
516
657
 
517
658
  def deconstruct_keys(keys)
518
- { opening: opening, name: name, closing: closing, location: location }
659
+ super.merge(opening: opening, name: name, closing: closing)
519
660
  end
520
661
  end
521
662
  end
@@ -41,10 +41,16 @@ module SyntaxTree
41
41
  private
42
42
 
43
43
  def parse_any_tag
44
- atleast do
45
- maybe { parse_html_comment } || maybe { parse_erb_tag } ||
46
- maybe { consume(:erb_comment) } || maybe { parse_html_element } ||
47
- maybe { parse_blank_line } || maybe { parse_chardata }
44
+ loop do
45
+ tag =
46
+ atleast do
47
+ maybe { parse_html_comment } || maybe { parse_erb_tag } ||
48
+ maybe { parse_erb_comment } || maybe { parse_html_element } ||
49
+ maybe { parse_new_line } || maybe { parse_chardata }
50
+ end
51
+
52
+ # Allow skipping empty CharData
53
+ return tag unless tag.skip?
48
54
  end
49
55
  end
50
56
 
@@ -62,10 +68,10 @@ module SyntaxTree
62
68
  # two or more newlines should be ONE blank line
63
69
  enum.yield :blank_line, $&, index, line
64
70
  line += $&.count("\n")
65
- when /\A(?: |\t|\n|\r\n)+/m
66
- # whitespace
67
- # enum.yield :whitespace, $&, index, line
68
- line += $&.count("\n")
71
+ when /\A\n/
72
+ # newlines
73
+ enum.yield :new_line, $&, index, line
74
+ line += 1
69
75
  when /\A<!--(.|\r?\n)*?-->/m
70
76
  # comments
71
77
  # <!-- this is a comment -->
@@ -97,8 +103,11 @@ module SyntaxTree
97
103
  # <
98
104
  enum.yield :open, $&, index, line
99
105
  state << :inside
100
- when /\A[^<]+/
101
- # plain text content
106
+ when /\A(?: |\t|\r)+/m
107
+ # whitespace
108
+ enum.yield :whitespace, $&, index, line
109
+ when /\A(?!\s+$)[^<\n]+/
110
+ # plain text content, but do not allow only white space
102
111
  # abc
103
112
  enum.yield :text, $&, index, line
104
113
  else
@@ -178,7 +187,7 @@ module SyntaxTree
178
187
  # the beginning of an ERB tag
179
188
  # <%
180
189
  enum.yield :erb_open, $&, index, line
181
- state << :erb
190
+ state << :erb_start
182
191
  when /\A[^<']+/
183
192
  # plain text content
184
193
  # abc
@@ -201,7 +210,7 @@ module SyntaxTree
201
210
  # the beginning of an ERB tag
202
211
  # <%
203
212
  enum.yield :erb_open, $&, index, line
204
- state << :erb
213
+ state << :erb_start
205
214
  when /\A[^<"]+/
206
215
  # plain text content
207
216
  # abc
@@ -251,7 +260,7 @@ module SyntaxTree
251
260
  # the beginning of an ERB tag
252
261
  # <%
253
262
  enum.yield :erb_open, $&, index, line
254
- state << :erb
263
+ state << :erb_start
255
264
  when /\A"/
256
265
  # the beginning of a string
257
266
  enum.yield :string_open_double_quote, $&, index, line
@@ -357,12 +366,18 @@ module SyntaxTree
357
366
  maybe { consume(:close) } || maybe { consume(:slash_close) }
358
367
  end
359
368
 
369
+ new_line = maybe { parse_new_line }
370
+
371
+ # Parse any whitespace after new lines
372
+ maybe { consume(:whitespace) }
373
+
360
374
  HtmlNode::OpeningTag.new(
361
375
  opening: opening,
362
376
  name: name,
363
377
  attributes: attributes,
364
378
  closing: closing,
365
- location: opening.location.to(closing.location)
379
+ location: opening.location.to(closing.location),
380
+ new_line: new_line
366
381
  )
367
382
  end
368
383
 
@@ -371,11 +386,14 @@ module SyntaxTree
371
386
  name = consume(:name)
372
387
  closing = consume(:close)
373
388
 
389
+ new_line = maybe { parse_new_line }
390
+
374
391
  HtmlNode::ClosingTag.new(
375
392
  opening: opening,
376
393
  name: name,
377
394
  closing: closing,
378
- location: opening.location.to(closing.location)
395
+ location: opening.location.to(closing.location),
396
+ new_line: new_line
379
397
  )
380
398
  end
381
399
 
@@ -450,6 +468,9 @@ module SyntaxTree
450
468
  end
451
469
 
452
470
  def parse_erb_if(erb_node)
471
+ # Skip any leading whitespace
472
+ maybe { consume(:whitespace) }
473
+
453
474
  elements =
454
475
  maybe { parse_until_erb(classes: [ErbElsif, ErbElse, ErbEnd]) } || []
455
476
 
@@ -513,11 +534,14 @@ module SyntaxTree
513
534
  end
514
535
 
515
536
  def parse_erb_end(erb_node)
537
+ new_line = maybe { parse_new_line }
538
+
516
539
  ErbEnd.new(
517
540
  opening_tag: erb_node.opening_tag,
518
541
  keyword: erb_node.keyword,
519
542
  content: nil,
520
543
  closing_tag: erb_node.closing_tag,
544
+ new_line: new_line,
521
545
  location: erb_node.location
522
546
  )
523
547
  end
@@ -540,12 +564,15 @@ module SyntaxTree
540
564
  )
541
565
  end
542
566
 
567
+ new_line = maybe { parse_new_line }
568
+
543
569
  erb_node =
544
570
  ErbNode.new(
545
571
  opening_tag: opening_tag,
546
572
  keyword: keyword,
547
573
  content: content,
548
574
  closing_tag: closing_tag,
575
+ new_line: new_line,
549
576
  location: opening_tag.location.to(closing_tag.location)
550
577
  )
551
578
 
@@ -602,6 +629,7 @@ module SyntaxTree
602
629
  maybe { parse_erb_do_close } || maybe { parse_erb_close } ||
603
630
  maybe { consume(:erb_code) }
604
631
  end
632
+
605
633
  items << result
606
634
 
607
635
  break if result.is_a?(ErbClose)
@@ -610,22 +638,46 @@ module SyntaxTree
610
638
  items
611
639
  end
612
640
 
613
- def parse_blank_line
614
- blank_line = consume(:blank_line)
641
+ # This method is called at the end of most tags, it fixes:
642
+ # 1. Parsing any new lines after the tag
643
+ # 2. Parsing any whitespace after the new lines
644
+ # The whitespace is just consumed
645
+ def parse_new_line
646
+ line_break =
647
+ atleast do
648
+ maybe { consume(:blank_line) } || maybe { consume(:new_line) }
649
+ end
650
+
651
+ maybe { consume(:whitespace) }
615
652
 
616
- CharData.new(value: blank_line, location: blank_line.location)
653
+ NewLine.new(
654
+ location: line_break.location,
655
+ count: line_break.value.count("\n")
656
+ )
617
657
  end
618
658
 
619
659
  def parse_erb_close
620
660
  closing = consume(:erb_close)
621
661
 
622
- ErbClose.new(location: closing.location, closing: closing)
662
+ new_line = maybe { parse_new_line }
663
+
664
+ ErbClose.new(
665
+ location: closing.location,
666
+ new_line: new_line,
667
+ closing: closing
668
+ )
623
669
  end
624
670
 
625
671
  def parse_erb_do_close
626
672
  closing = consume(:erb_do_close)
627
673
 
628
- ErbDoClose.new(location: closing.location, closing: closing)
674
+ new_line = maybe { parse_new_line }
675
+
676
+ ErbDoClose.new(
677
+ location: closing.location,
678
+ new_line: new_line,
679
+ closing: closing
680
+ )
629
681
  end
630
682
 
631
683
  def parse_html_string
@@ -715,7 +767,15 @@ module SyntaxTree
715
767
  values.first
716
768
  end
717
769
 
718
- CharData.new(value: token, location: token.location) if token
770
+ new_line = maybe { parse_new_line }
771
+
772
+ if token&.value
773
+ CharData.new(
774
+ value: token,
775
+ location: token.location,
776
+ new_line: new_line
777
+ )
778
+ end
719
779
  end
720
780
 
721
781
  def parse_doctype
@@ -723,10 +783,13 @@ module SyntaxTree
723
783
  name = consume(:name)
724
784
  closing = consume(:close)
725
785
 
786
+ new_line = maybe { parse_new_line }
787
+
726
788
  Doctype.new(
727
789
  opening: opening,
728
790
  name: name,
729
791
  closing: closing,
792
+ new_line: new_line,
730
793
  location: opening.location.to(closing.location)
731
794
  )
732
795
  end
@@ -734,7 +797,25 @@ module SyntaxTree
734
797
  def parse_html_comment
735
798
  comment = consume(:html_comment)
736
799
 
737
- HtmlComment.new(token: comment, location: comment.location)
800
+ new_line = maybe { parse_new_line }
801
+
802
+ HtmlComment.new(
803
+ token: comment,
804
+ new_line: new_line,
805
+ location: comment.location
806
+ )
807
+ end
808
+
809
+ def parse_erb_comment
810
+ comment = consume(:erb_comment)
811
+
812
+ new_line = maybe { parse_new_line }
813
+
814
+ ErbComment.new(
815
+ token: comment,
816
+ new_line: new_line,
817
+ location: comment.location
818
+ )
738
819
  end
739
820
  end
740
821
  end
@@ -108,7 +108,7 @@ module SyntaxTree
108
108
  end
109
109
 
110
110
  def visit_erb_end(node)
111
- q.pp("erb_end")
111
+ q.pp("(erb_end)")
112
112
  end
113
113
 
114
114
  # Visit an ErbContent node.
@@ -131,6 +131,10 @@ module SyntaxTree
131
131
  visit_node("char_data", node)
132
132
  end
133
133
 
134
+ def visit_new_line(node)
135
+ node.count > 1 ? q.text("(new_line blank)") : q.text("(new_line)")
136
+ end
137
+
134
138
  def visit_erb_close(node)
135
139
  visit(node.closing)
136
140
  end
@@ -148,6 +152,10 @@ module SyntaxTree
148
152
  visit_node("html_comment", node)
149
153
  end
150
154
 
155
+ def visit_erb_comment(node)
156
+ visit_node("erb_comment", node)
157
+ end
158
+
151
159
  private
152
160
 
153
161
  # A generic visit node function for how we pretty print nodes.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SyntaxTree
4
4
  module ERB
5
- VERSION = "0.9.5"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
@@ -26,5 +26,6 @@ module SyntaxTree
26
26
  end
27
27
  end
28
28
 
29
+ register_handler(".html.erb", ERB)
29
30
  register_handler(".erb", ERB)
30
31
  end
data/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "scripts": {
3
+ "prepare": "husky install"
4
+ },
5
+ "lint-staged": {
6
+ "lib/**/*.rb": [
7
+ "bundle exec stree write"
8
+ ]
9
+ }
10
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: w_syntax_tree-erb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-07-02 00:00:00.000000000 Z
12
+ date: 2023-08-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: prettier_print
@@ -121,6 +121,8 @@ files:
121
121
  - ".github/workflows/auto-merge.yml"
122
122
  - ".github/workflows/main.yml"
123
123
  - ".gitignore"
124
+ - ".husky/pre-commit"
125
+ - ".vscode/launch.json"
124
126
  - CHANGELOG.md
125
127
  - Gemfile
126
128
  - Gemfile.lock
@@ -135,6 +137,7 @@ files:
135
137
  - lib/syntax_tree/erb/pretty_print.rb
136
138
  - lib/syntax_tree/erb/version.rb
137
139
  - lib/syntax_tree/erb/visitor.rb
140
+ - package.json
138
141
  - syntax_tree-erb.gemspec
139
142
  homepage: https://github.com/davidwessman/syntax_tree-erb
140
143
  licenses:
@@ -156,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
159
  - !ruby/object:Gem::Version
157
160
  version: '0'
158
161
  requirements: []
159
- rubygems_version: 3.4.1
162
+ rubygems_version: 3.4.19
160
163
  signing_key:
161
164
  specification_version: 4
162
165
  summary: Syntax Tree support for ERB