w_syntax_tree-erb 0.9.4 → 0.10.0

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: 14545f225ceddbf06a7e0d3c53acb6e167e57a52eb603685de89fd474db64532
4
- data.tar.gz: 2b0c5a2927d6ab3b9af64648e4991e0f02deb74803b57f2dac6e8c832765e8a5
3
+ metadata.gz: e48aeaba8ef070f5f13bdd166cf4dd9f1f1e4693de8537d2dbf5b86f90092de6
4
+ data.tar.gz: ade025cc4a31c3f0b5461650a97ff2074de6dc868197ab09a2d2b0c047b1d0cc
5
5
  SHA512:
6
- metadata.gz: 34cd2b99695f54e2a11fd15fc93a98e953600b5bae3e4edbe5c3d51fb0ea53e4ba22b91d099b3ae9036c0428b77b2c6fe321eb68699b5d7b59d0eae5910dd144
7
- data.tar.gz: 779f4949a6684b4a18a89096c613518e656cad687877f1b23eadf4cc4969341681ee2926232a009a8b13662bd9e5ee246abfa8b5757f5c327fea762bc6b14756
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,36 @@ 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
+
18
+ ## [0.9.5] - 2023-07-02
19
+
20
+ - Fixes ruby comment in ERB-tag included VoidStatement
21
+ Example:
22
+
23
+ ```erb
24
+ <% # this is a comment %>
25
+ ```
26
+
27
+ Output:
28
+
29
+ ```diff
30
+ -<%
31
+ -
32
+ - # this is a comment
33
+ -%>
34
+ +<% # this is a comment %>
35
+ ```
36
+
37
+ - Updates versions in Bundler
38
+
9
39
  ## [0.9.4] - 2023-07-01
10
40
 
11
41
  - Inline even more empty HTML-tags
@@ -40,5 +70,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
40
70
  - Can format a lot of .html.erb-syntax and works as a plugin to syntax_tree.
41
71
  - This is still early and there are a lot of different weird syntaxes out there.
42
72
 
43
- [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
44
80
  [0.9.0]: https://github.com/davidwessman/syntax_tree-erb/compare/419727a73af94057ca0980733e69ac8b4d52fdf4...v0.9.0
data/Gemfile.lock CHANGED
@@ -1,15 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- w_syntax_tree-erb (0.9.4)
5
- prettier_print (>= 1.2.0)
6
- syntax_tree (>= 6.1.1)
4
+ w_syntax_tree-erb (0.10.0)
5
+ prettier_print (~> 1.2, >= 1.2.0)
6
+ syntax_tree (~> 6.1, >= 6.1.1)
7
7
 
8
8
  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)
@@ -28,10 +28,10 @@ PLATFORMS
28
28
  x86_64-linux
29
29
 
30
30
  DEPENDENCIES
31
- bundler
32
- minitest
33
- rake
34
- simplecov
31
+ bundler (~> 2)
32
+ minitest (~> 5)
33
+ rake (~> 13)
34
+ simplecov (~> 0.22)
35
35
  w_syntax_tree-erb!
36
36
 
37
37
  BUNDLED WITH
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
 
@@ -118,18 +140,19 @@ module SyntaxTree
118
140
  if node.value.is_a?(String)
119
141
  output_rows(node.value.split("\n"))
120
142
  else
121
- child_nodes = node.value&.statements&.child_nodes || []
143
+ nodes = node.value&.statements&.child_nodes || []
144
+ nodes = nodes.reject { |node| node.is_a?(SyntaxTree::VoidStmt) }
122
145
 
123
- if child_nodes.size == 1
146
+ if nodes.size == 1
124
147
  q.text(" ")
125
- q.seplist(child_nodes, -> { q.breakable("") }) do |child_node|
148
+ q.seplist(nodes, -> { q.breakable("") }) do |child_node|
126
149
  format_statement(child_node)
127
150
  end
128
151
  q.text(" ")
129
- elsif child_nodes.size > 1
152
+ elsif nodes.size > 1
130
153
  q.indent do
131
154
  q.breakable("")
132
- q.seplist(child_nodes, -> { q.breakable("") }) do |child_node|
155
+ q.seplist(nodes, -> { q.breakable("") }) do |child_node|
133
156
  format_statement(child_node)
134
157
  end
135
158
  end
@@ -145,7 +168,7 @@ module SyntaxTree
145
168
  formatter.flush
146
169
  rows = formatter.output.join.split("\n")
147
170
 
148
- output_rows(formatter.output.join.split("\n"))
171
+ output_rows(rows)
149
172
  end
150
173
 
151
174
  def output_rows(rows)
@@ -169,9 +192,14 @@ module SyntaxTree
169
192
  visit(child_node)
170
193
  end
171
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(" ")
172
201
  end
173
202
 
174
- q.breakable(node.closing.value == "/>" ? " " : "")
175
203
  visit(node.closing)
176
204
  end
177
205
  end
@@ -207,15 +235,20 @@ module SyntaxTree
207
235
  visit(node.token)
208
236
  end
209
237
 
238
+ def visit_erb_comment(node)
239
+ visit(node.token)
240
+ end
241
+
210
242
  # Visit a CharData node.
211
243
  def visit_char_data(node)
212
- lines = node.value.value.strip.split("\n")
244
+ return if node.value.value.strip.empty?
213
245
 
214
- if lines.size > 0
215
- q.seplist(lines, -> { q.breakable(indent: false) }) do |line|
216
- q.text(line)
217
- end
218
- 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
219
252
  end
220
253
 
221
254
  # Visit a Doctype node.
@@ -229,6 +262,49 @@ module SyntaxTree
229
262
  end
230
263
  end
231
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
+
232
308
  def erb_print_width(node)
233
309
  # Set the width to maximum if we have an IfNode or IfOp,
234
310
  # we cannot format them purely with SyntaxTree because the ERB-syntax will be unparseable.
@@ -246,6 +322,60 @@ module SyntaxTree
246
322
  check_for_if_statement(child_node)
247
323
  end
248
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
249
379
  end
250
380
  end
251
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.4"
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
+ }
@@ -23,11 +23,11 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = %w[lib]
25
25
 
26
- spec.add_dependency "prettier_print", ">= 1.2.0"
27
- spec.add_dependency "syntax_tree", ">= 6.1.1"
26
+ spec.add_runtime_dependency "prettier_print", "~> 1.2", ">= 1.2.0"
27
+ spec.add_runtime_dependency "syntax_tree", "~> 6.1", ">= 6.1.1"
28
28
 
29
- spec.add_development_dependency "bundler"
30
- spec.add_development_dependency "minitest"
31
- spec.add_development_dependency "rake"
32
- spec.add_development_dependency "simplecov"
29
+ spec.add_development_dependency "bundler", "~> 2"
30
+ spec.add_development_dependency "minitest", "~> 5"
31
+ spec.add_development_dependency "rake", "~> 13"
32
+ spec.add_development_dependency "simplecov", "~> 0.22"
33
33
  end
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.4
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
@@ -9,12 +9,15 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-07-01 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
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.2'
18
21
  - - ">="
19
22
  - !ruby/object:Gem::Version
20
23
  version: 1.2.0
@@ -22,6 +25,9 @@ dependencies:
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
24
27
  requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '1.2'
25
31
  - - ">="
26
32
  - !ruby/object:Gem::Version
27
33
  version: 1.2.0
@@ -29,6 +35,9 @@ dependencies:
29
35
  name: syntax_tree
30
36
  requirement: !ruby/object:Gem::Requirement
31
37
  requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '6.1'
32
41
  - - ">="
33
42
  - !ruby/object:Gem::Version
34
43
  version: 6.1.1
@@ -36,6 +45,9 @@ dependencies:
36
45
  prerelease: false
37
46
  version_requirements: !ruby/object:Gem::Requirement
38
47
  requirements:
48
+ - - "~>"
49
+ - !ruby/object:Gem::Version
50
+ version: '6.1'
39
51
  - - ">="
40
52
  - !ruby/object:Gem::Version
41
53
  version: 6.1.1
@@ -43,58 +55,58 @@ dependencies:
43
55
  name: bundler
44
56
  requirement: !ruby/object:Gem::Requirement
45
57
  requirements:
46
- - - ">="
58
+ - - "~>"
47
59
  - !ruby/object:Gem::Version
48
- version: '0'
60
+ version: '2'
49
61
  type: :development
50
62
  prerelease: false
51
63
  version_requirements: !ruby/object:Gem::Requirement
52
64
  requirements:
53
- - - ">="
65
+ - - "~>"
54
66
  - !ruby/object:Gem::Version
55
- version: '0'
67
+ version: '2'
56
68
  - !ruby/object:Gem::Dependency
57
69
  name: minitest
58
70
  requirement: !ruby/object:Gem::Requirement
59
71
  requirements:
60
- - - ">="
72
+ - - "~>"
61
73
  - !ruby/object:Gem::Version
62
- version: '0'
74
+ version: '5'
63
75
  type: :development
64
76
  prerelease: false
65
77
  version_requirements: !ruby/object:Gem::Requirement
66
78
  requirements:
67
- - - ">="
79
+ - - "~>"
68
80
  - !ruby/object:Gem::Version
69
- version: '0'
81
+ version: '5'
70
82
  - !ruby/object:Gem::Dependency
71
83
  name: rake
72
84
  requirement: !ruby/object:Gem::Requirement
73
85
  requirements:
74
- - - ">="
86
+ - - "~>"
75
87
  - !ruby/object:Gem::Version
76
- version: '0'
88
+ version: '13'
77
89
  type: :development
78
90
  prerelease: false
79
91
  version_requirements: !ruby/object:Gem::Requirement
80
92
  requirements:
81
- - - ">="
93
+ - - "~>"
82
94
  - !ruby/object:Gem::Version
83
- version: '0'
95
+ version: '13'
84
96
  - !ruby/object:Gem::Dependency
85
97
  name: simplecov
86
98
  requirement: !ruby/object:Gem::Requirement
87
99
  requirements:
88
- - - ">="
100
+ - - "~>"
89
101
  - !ruby/object:Gem::Version
90
- version: '0'
102
+ version: '0.22'
91
103
  type: :development
92
104
  prerelease: false
93
105
  version_requirements: !ruby/object:Gem::Requirement
94
106
  requirements:
95
- - - ">="
107
+ - - "~>"
96
108
  - !ruby/object:Gem::Version
97
- version: '0'
109
+ version: '0.22'
98
110
  description:
99
111
  email:
100
112
  - kddnewton@gmail.com
@@ -109,6 +121,8 @@ files:
109
121
  - ".github/workflows/auto-merge.yml"
110
122
  - ".github/workflows/main.yml"
111
123
  - ".gitignore"
124
+ - ".husky/pre-commit"
125
+ - ".vscode/launch.json"
112
126
  - CHANGELOG.md
113
127
  - Gemfile
114
128
  - Gemfile.lock
@@ -123,6 +137,7 @@ files:
123
137
  - lib/syntax_tree/erb/pretty_print.rb
124
138
  - lib/syntax_tree/erb/version.rb
125
139
  - lib/syntax_tree/erb/visitor.rb
140
+ - package.json
126
141
  - syntax_tree-erb.gemspec
127
142
  homepage: https://github.com/davidwessman/syntax_tree-erb
128
143
  licenses:
@@ -144,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
159
  - !ruby/object:Gem::Version
145
160
  version: '0'
146
161
  requirements: []
147
- rubygems_version: 3.4.1
162
+ rubygems_version: 3.4.19
148
163
  signing_key:
149
164
  specification_version: 4
150
165
  summary: Syntax Tree support for ERB