w_syntax_tree-erb 0.9.5 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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