w_syntax_tree-erb 0.9.5 → 0.10.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: 7625d38e4764dc486d61c0141a4e25892efa6fc993779b08885110dd55f830e6
4
- data.tar.gz: 1551342a3b0550f0e59bf92c184586f0b8ea401bb2c4f55058e6fb1b7d0759f7
3
+ metadata.gz: a76706871104d4d2ea966b498103883d197378ec7e93fb339430b82d8e70d7ee
4
+ data.tar.gz: 2d78160b39db8ddf8a62aaa132c3d935870d5db8f2383250a0502d7475d1bd1f
5
5
  SHA512:
6
- metadata.gz: c737cc77acef8c790b3e08a99aacc509e01e69ea67a70ecfa2684db9034bcb65c4ec7cb705fafe6e8c147a8f0a4237d015ebeedddd213e26c7a75fe123348618
7
- data.tar.gz: 38f27bdf74980a3fae073b2fc5ff5e2114eacfe69886da0b819e62ac5547ae220e369de722a0284aa556dfce2f1e00e3b61364838e43438961119891fdf4f2b8
6
+ metadata.gz: 111165a4191e3dd16baf66ef3ab1b1607a4ea1303113a63b5bf01a49d6b0ff0949ff313fb3ba6ada0e42a04de7809294738dc6b079537590915b3b764ac4b030
7
+ data.tar.gz: 8ff9ea2143768e7eb7ee067688b4ed1b33f05412f8bf381782376586edc7d958f3f53c4c28042320f583408f0775bee00ecedda7df8c145598b874cf8bcf2046
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,21 @@ 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.1] - 2023-08-20
10
+
11
+ ### Added
12
+
13
+ - Allow `DOCTYPE` to be after other tags, to work with e.g. ERB-tags on first line.
14
+
15
+ ## [0.10.0] - 2023-08-20
16
+
17
+ - Changes how whitespace and newlines are handled.
18
+ - Supports syntax like:
19
+
20
+ ```erb
21
+ <%= part %> / <%= total %> (<%= percentage %>%)
22
+ ```
23
+
9
24
  ## [0.9.5] - 2023-07-02
10
25
 
11
26
  - Fixes ruby comment in ERB-tag included VoidStatement
@@ -61,5 +76,12 @@ Output:
61
76
  - Can format a lot of .html.erb-syntax and works as a plugin to syntax_tree.
62
77
  - This is still early and there are a lot of different weird syntaxes out there.
63
78
 
64
- [unreleased]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.0...HEAD
79
+ [unreleased]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.10.1...HEAD
80
+ [0.10.1]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.10.0...v0.10.1
81
+ [0.10.0]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.5...v0.10.0
82
+ [0.9.5]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.4...v0.9.5
83
+ [0.9.4]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.3...v0.9.4
84
+ [0.9.3]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.2...v0.9.3
85
+ [0.9.2]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.1...v0.9.2
86
+ [0.9.1]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.0...v0.9.1
65
87
  [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.1)
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
@@ -20,16 +20,16 @@ module SyntaxTree
20
20
  def initialize(source)
21
21
  @source = source
22
22
  @tokens = make_tokens
23
+ @found_doctype = false
23
24
  end
24
25
 
25
26
  def parse
26
- doctype = maybe { parse_doctype }
27
27
  elements = many { parse_any_tag }
28
28
 
29
29
  location =
30
30
  elements.first.location.to(elements.last.location) if elements.any?
31
31
 
32
- Document.new(elements: [doctype].compact + elements, location: location)
32
+ Document.new(elements: elements, location: location)
33
33
  end
34
34
 
35
35
  def debug_tokens
@@ -41,10 +41,24 @@ 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_doctype } || maybe { parse_html_comment } ||
48
+ maybe { parse_erb_tag } || maybe { parse_erb_comment } ||
49
+ maybe { parse_html_element } || maybe { parse_new_line } ||
50
+ maybe { parse_chardata }
51
+ end
52
+
53
+ if tag.is_a?(Doctype)
54
+ if @found_doctype
55
+ raise(ParseError, "Only one doctype element is allowed")
56
+ else
57
+ @found_doctype = true
58
+ end
59
+ end
60
+ # Allow skipping empty CharData
61
+ return tag unless tag.skip?
48
62
  end
49
63
  end
50
64
 
@@ -62,10 +76,10 @@ module SyntaxTree
62
76
  # two or more newlines should be ONE blank line
63
77
  enum.yield :blank_line, $&, index, line
64
78
  line += $&.count("\n")
65
- when /\A(?: |\t|\n|\r\n)+/m
66
- # whitespace
67
- # enum.yield :whitespace, $&, index, line
68
- line += $&.count("\n")
79
+ when /\A\n/
80
+ # newlines
81
+ enum.yield :new_line, $&, index, line
82
+ line += 1
69
83
  when /\A<!--(.|\r?\n)*?-->/m
70
84
  # comments
71
85
  # <!-- this is a comment -->
@@ -97,8 +111,11 @@ module SyntaxTree
97
111
  # <
98
112
  enum.yield :open, $&, index, line
99
113
  state << :inside
100
- when /\A[^<]+/
101
- # plain text content
114
+ when /\A(?: |\t|\r)+/m
115
+ # whitespace
116
+ enum.yield :whitespace, $&, index, line
117
+ when /\A(?!\s+$)[^<\n]+/
118
+ # plain text content, but do not allow only white space
102
119
  # abc
103
120
  enum.yield :text, $&, index, line
104
121
  else
@@ -178,7 +195,7 @@ module SyntaxTree
178
195
  # the beginning of an ERB tag
179
196
  # <%
180
197
  enum.yield :erb_open, $&, index, line
181
- state << :erb
198
+ state << :erb_start
182
199
  when /\A[^<']+/
183
200
  # plain text content
184
201
  # abc
@@ -201,7 +218,7 @@ module SyntaxTree
201
218
  # the beginning of an ERB tag
202
219
  # <%
203
220
  enum.yield :erb_open, $&, index, line
204
- state << :erb
221
+ state << :erb_start
205
222
  when /\A[^<"]+/
206
223
  # plain text content
207
224
  # abc
@@ -251,7 +268,7 @@ module SyntaxTree
251
268
  # the beginning of an ERB tag
252
269
  # <%
253
270
  enum.yield :erb_open, $&, index, line
254
- state << :erb
271
+ state << :erb_start
255
272
  when /\A"/
256
273
  # the beginning of a string
257
274
  enum.yield :string_open_double_quote, $&, index, line
@@ -357,12 +374,18 @@ module SyntaxTree
357
374
  maybe { consume(:close) } || maybe { consume(:slash_close) }
358
375
  end
359
376
 
377
+ new_line = maybe { parse_new_line }
378
+
379
+ # Parse any whitespace after new lines
380
+ maybe { consume(:whitespace) }
381
+
360
382
  HtmlNode::OpeningTag.new(
361
383
  opening: opening,
362
384
  name: name,
363
385
  attributes: attributes,
364
386
  closing: closing,
365
- location: opening.location.to(closing.location)
387
+ location: opening.location.to(closing.location),
388
+ new_line: new_line
366
389
  )
367
390
  end
368
391
 
@@ -371,11 +394,14 @@ module SyntaxTree
371
394
  name = consume(:name)
372
395
  closing = consume(:close)
373
396
 
397
+ new_line = maybe { parse_new_line }
398
+
374
399
  HtmlNode::ClosingTag.new(
375
400
  opening: opening,
376
401
  name: name,
377
402
  closing: closing,
378
- location: opening.location.to(closing.location)
403
+ location: opening.location.to(closing.location),
404
+ new_line: new_line
379
405
  )
380
406
  end
381
407
 
@@ -450,6 +476,9 @@ module SyntaxTree
450
476
  end
451
477
 
452
478
  def parse_erb_if(erb_node)
479
+ # Skip any leading whitespace
480
+ maybe { consume(:whitespace) }
481
+
453
482
  elements =
454
483
  maybe { parse_until_erb(classes: [ErbElsif, ErbElse, ErbEnd]) } || []
455
484
 
@@ -513,11 +542,14 @@ module SyntaxTree
513
542
  end
514
543
 
515
544
  def parse_erb_end(erb_node)
545
+ new_line = maybe { parse_new_line }
546
+
516
547
  ErbEnd.new(
517
548
  opening_tag: erb_node.opening_tag,
518
549
  keyword: erb_node.keyword,
519
550
  content: nil,
520
551
  closing_tag: erb_node.closing_tag,
552
+ new_line: new_line,
521
553
  location: erb_node.location
522
554
  )
523
555
  end
@@ -540,12 +572,15 @@ module SyntaxTree
540
572
  )
541
573
  end
542
574
 
575
+ new_line = maybe { parse_new_line }
576
+
543
577
  erb_node =
544
578
  ErbNode.new(
545
579
  opening_tag: opening_tag,
546
580
  keyword: keyword,
547
581
  content: content,
548
582
  closing_tag: closing_tag,
583
+ new_line: new_line,
549
584
  location: opening_tag.location.to(closing_tag.location)
550
585
  )
551
586
 
@@ -602,6 +637,7 @@ module SyntaxTree
602
637
  maybe { parse_erb_do_close } || maybe { parse_erb_close } ||
603
638
  maybe { consume(:erb_code) }
604
639
  end
640
+
605
641
  items << result
606
642
 
607
643
  break if result.is_a?(ErbClose)
@@ -610,22 +646,46 @@ module SyntaxTree
610
646
  items
611
647
  end
612
648
 
613
- def parse_blank_line
614
- blank_line = consume(:blank_line)
649
+ # This method is called at the end of most tags, it fixes:
650
+ # 1. Parsing any new lines after the tag
651
+ # 2. Parsing any whitespace after the new lines
652
+ # The whitespace is just consumed
653
+ def parse_new_line
654
+ line_break =
655
+ atleast do
656
+ maybe { consume(:blank_line) } || maybe { consume(:new_line) }
657
+ end
658
+
659
+ maybe { consume(:whitespace) }
615
660
 
616
- CharData.new(value: blank_line, location: blank_line.location)
661
+ NewLine.new(
662
+ location: line_break.location,
663
+ count: line_break.value.count("\n")
664
+ )
617
665
  end
618
666
 
619
667
  def parse_erb_close
620
668
  closing = consume(:erb_close)
621
669
 
622
- ErbClose.new(location: closing.location, closing: closing)
670
+ new_line = maybe { parse_new_line }
671
+
672
+ ErbClose.new(
673
+ location: closing.location,
674
+ new_line: new_line,
675
+ closing: closing
676
+ )
623
677
  end
624
678
 
625
679
  def parse_erb_do_close
626
680
  closing = consume(:erb_do_close)
627
681
 
628
- ErbDoClose.new(location: closing.location, closing: closing)
682
+ new_line = maybe { parse_new_line }
683
+
684
+ ErbDoClose.new(
685
+ location: closing.location,
686
+ new_line: new_line,
687
+ closing: closing
688
+ )
629
689
  end
630
690
 
631
691
  def parse_html_string
@@ -715,7 +775,15 @@ module SyntaxTree
715
775
  values.first
716
776
  end
717
777
 
718
- CharData.new(value: token, location: token.location) if token
778
+ new_line = maybe { parse_new_line }
779
+
780
+ if token&.value
781
+ CharData.new(
782
+ value: token,
783
+ location: token.location,
784
+ new_line: new_line
785
+ )
786
+ end
719
787
  end
720
788
 
721
789
  def parse_doctype
@@ -723,10 +791,13 @@ module SyntaxTree
723
791
  name = consume(:name)
724
792
  closing = consume(:close)
725
793
 
794
+ new_line = maybe { parse_new_line }
795
+
726
796
  Doctype.new(
727
797
  opening: opening,
728
798
  name: name,
729
799
  closing: closing,
800
+ new_line: new_line,
730
801
  location: opening.location.to(closing.location)
731
802
  )
732
803
  end
@@ -734,7 +805,25 @@ module SyntaxTree
734
805
  def parse_html_comment
735
806
  comment = consume(:html_comment)
736
807
 
737
- HtmlComment.new(token: comment, location: comment.location)
808
+ new_line = maybe { parse_new_line }
809
+
810
+ HtmlComment.new(
811
+ token: comment,
812
+ new_line: new_line,
813
+ location: comment.location
814
+ )
815
+ end
816
+
817
+ def parse_erb_comment
818
+ comment = consume(:erb_comment)
819
+
820
+ new_line = maybe { parse_new_line }
821
+
822
+ ErbComment.new(
823
+ token: comment,
824
+ new_line: new_line,
825
+ location: comment.location
826
+ )
738
827
  end
739
828
  end
740
829
  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.1"
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.1
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