ast_transform 3.1.0 → 3.1.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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/ast_transform/layout.rb +13 -4
- data/lib/ast_transform/line_aligned_emitter.rb +5 -2
- data/lib/ast_transform/statement_renderer.rb +22 -0
- data/lib/ast_transform/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb38ad75ff6d31e4f8da1d1b272968b3d65393a3acaf92b914be2c0d35ebdb32
|
|
4
|
+
data.tar.gz: f2621b3116184b0f7b7eb87a9a4b4af184f685e4399e5b52761b1deb1f54c998
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3ff9177f5bdd86e95af2406aeb675a53cd93917a63faa2fdf26bcfb12bc6365e415dbadb576247f7d134a2a36a7866d46cca17626351fb648f156379e3ba5473
|
|
7
|
+
data.tar.gz: 054fe46ee27bc9cda5f18b163460f62a94c496cc3993407e79a3592079d3011c56c78d300db0639d685749e47a704a0a3e6d3a1915fbcbd044de7a7b6a6c173b
|
data/Gemfile.lock
CHANGED
data/lib/ast_transform/layout.rb
CHANGED
|
@@ -9,6 +9,7 @@ module ASTTransform
|
|
|
9
9
|
class Layout
|
|
10
10
|
def initialize
|
|
11
11
|
@lines = []
|
|
12
|
+
@sealed = false
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
# The line number currently being written; the next fresh line would be +cursor + 1+.
|
|
@@ -21,7 +22,10 @@ module ASTTransform
|
|
|
21
22
|
# +column+ — cosmetic only (leading whitespace is never significant in emitted code), but it keeps the artifact
|
|
22
23
|
# visually close to the source. Packed text ignores the column, as do continuation lines (they keep their own
|
|
23
24
|
# relative indentation).
|
|
24
|
-
|
|
25
|
+
#
|
|
26
|
+
# +seal+ declares the text's last line unextendable (the caller's property — e.g. it terminates a heredoc,
|
|
27
|
+
# whose terminator must stand alone): a subsequent pack opens a fresh line instead of joining it.
|
|
28
|
+
def place(target_line, text, column: nil, seal: false)
|
|
25
29
|
first, *rest = text.split("\n")
|
|
26
30
|
|
|
27
31
|
if target_line && target_line > @lines.size
|
|
@@ -32,22 +36,27 @@ module ASTTransform
|
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
@lines.concat(rest)
|
|
39
|
+
@sealed = seal
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
# Appends +text+ on a new line unconditionally — for text that must never be `;`-packed after a statement
|
|
38
43
|
# (e.g. keywords).
|
|
39
44
|
def place_on_fresh_line(text)
|
|
40
45
|
@lines << text
|
|
46
|
+
@sealed = false
|
|
41
47
|
end
|
|
42
48
|
|
|
43
|
-
# Appends +text+ to the current line with a `; ` separator
|
|
44
|
-
#
|
|
49
|
+
# Appends +text+ to the current line with a `; ` separator — unless the current line is sealed, in which case
|
|
50
|
+
# the text opens a fresh line (alignment degrades by one more line; validity is preserved). The last line is
|
|
51
|
+
# never blank here: padding blanks are only created inside +place+, which immediately overwrites the padded
|
|
52
|
+
# line.
|
|
45
53
|
def pack(text)
|
|
46
|
-
if @lines.empty?
|
|
54
|
+
if @lines.empty? || @sealed
|
|
47
55
|
@lines << text
|
|
48
56
|
else
|
|
49
57
|
@lines[-1] = "#{@lines.last}; #{text}"
|
|
50
58
|
end
|
|
59
|
+
@sealed = false
|
|
51
60
|
end
|
|
52
61
|
|
|
53
62
|
# @return [String] the laid-out text, with a trailing newline.
|
|
@@ -71,7 +71,9 @@ module ASTTransform
|
|
|
71
71
|
if recursive_container?(node)
|
|
72
72
|
emit_container(node, layout, renderer)
|
|
73
73
|
else
|
|
74
|
-
|
|
74
|
+
render = renderer.aligned_render(node)
|
|
75
|
+
# A render in heredoc form seals its line: nothing may pack after the terminator (issue #16).
|
|
76
|
+
layout.place(node.loc&.line, render, column: node.loc&.column, seal: renderer.line_terminal?(render))
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
|
|
@@ -145,7 +147,8 @@ module ASTTransform
|
|
|
145
147
|
# nested statements align.
|
|
146
148
|
def emit_container(node, layout, renderer)
|
|
147
149
|
opener, closer = container_delimiters(node, renderer)
|
|
148
|
-
|
|
150
|
+
# An opener can carry a heredoc too (e.g. a block call with a heredoc argument) — seal it like a statement.
|
|
151
|
+
layout.place(node.loc&.line, opener, column: node.loc&.column, seal: renderer.line_terminal?(opener))
|
|
149
152
|
emit_body(container_body(node), layout, renderer)
|
|
150
153
|
layout.place(closer_line(node), closer, column: closer_column(node))
|
|
151
154
|
end
|
|
@@ -62,8 +62,30 @@ module ASTTransform
|
|
|
62
62
|
compress_to_single_line(render) || render
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# Whether +render+ must be the last text on its final line: a render ending on a heredoc terminator (Unparser
|
|
66
|
+
# falls back to `<<-HEREDOC` form when its quoted-string round-trip fails) cannot have anything `;`-packed
|
|
67
|
+
# after it — the terminator must stand alone, so a join unterminates the heredoc. Probed by re-parse, the same
|
|
68
|
+
# verification style as +compress_to_single_line+, behind a cheap textual gate: only a heredoc makes a last
|
|
69
|
+
# line unextendable, and every heredoc opener contains +<<+. A false positive (the probe failing for another
|
|
70
|
+
# reason, e.g. an isolated container opener carrying a heredoc argument) costs one line of alignment, never
|
|
71
|
+
# correctness.
|
|
72
|
+
def line_terminal?(render)
|
|
73
|
+
return false unless render.include?('<<')
|
|
74
|
+
|
|
75
|
+
# chomp: heredoc renders end with a trailing newline; the probe must extend the last *line* (where the
|
|
76
|
+
# layout would pack), not append below it.
|
|
77
|
+
!parses?("#{render.chomp}; nil")
|
|
78
|
+
end
|
|
79
|
+
|
|
65
80
|
private
|
|
66
81
|
|
|
82
|
+
def parses?(source)
|
|
83
|
+
Unparser.parse(source)
|
|
84
|
+
true
|
|
85
|
+
rescue Parser::SyntaxError
|
|
86
|
+
false
|
|
87
|
+
end
|
|
88
|
+
|
|
67
89
|
def compress_to_single_line(render)
|
|
68
90
|
candidate = render.split("\n").map(&:strip).join('; ')
|
|
69
91
|
# Both sides parsed without scope context, so lvar/send ambiguity cancels out; equality means the newline
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ast_transform
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.1.
|
|
4
|
+
version: 3.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jean-Philippe Duchesne
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: parser
|