Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 77cd6cf

Browse files
committed
Support formatting from a non-main ractor
1 parent a743772 commit 77cd6cf

14 files changed

+166
-81
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
2626
- `Return0` is no longer a node. Instead if has been folded into the `Return` node. The `Return` node can now have its `arguments` field be `nil`. Consequently, the `visit_return0` method has been removed from the visitor interface. If you were previously using this method, you should now use `visit_return` instead.
2727
- The `ArgsForward`, `Redo`, `Retry`, and `ZSuper` nodes no longer have `value` fields associated with them (which were always string literals corresponding to the keyword being used).
2828
- The `Command` and `CommandCall` nodes now has `block` attributes on them. These attributes are used in the place where you would previously have had a `MethodAddBlock` structure. Where before the `MethodAddBlock` would have the command and block as its two children, you now just have one command node with the `block` attribute set to the `Block` node.
29+
- Previously the formatting options were defined on an unfrozen hash called `SyntaxTree::Formatter::OPTIONS`. It was globally mutable, which made it impossible to reference from within a Ractor. As such, it has now been replaced with `SyntaxTree::Formatter::Options.new` which creates a new options object instance that can be modified without impacting global state. As a part of this change, formatting can now be performed from within a non-main Ractor. In order to check if the `plugin/single_quotes` plugin has been loaded, check if `SyntaxTree::Formatter::SINGLE_QUOTES` is defined. In order to check if the `plugin/trailing_comma` plugin has been loaded, check if `SyntaxTree::Formatter::TRAILING_COMMA` is defined.
2930

3031
## [4.3.0] - 2022-10-28
3132

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ PATH
22
remote: .
33
specs:
44
syntax_tree (4.3.0)
5-
prettier_print (>= 1.0.2)
5+
prettier_print (>= 1.1.0)
66

77
GEM
88
remote: https://rubygems.org/
@@ -14,7 +14,7 @@ GEM
1414
parallel (1.22.1)
1515
parser (3.1.2.1)
1616
ast (~> 2.4.1)
17-
prettier_print (1.0.2)
17+
prettier_print (1.1.0)
1818
rainbow (3.1.1)
1919
rake (13.0.6)
2020
regexp_parser (2.6.0)

lib/syntax_tree.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,12 @@ def self.parse(source)
5454
end
5555

5656
# Parses the given source and returns the formatted source.
57-
def self.format(source, maxwidth = DEFAULT_PRINT_WIDTH)
58-
formatter = Formatter.new(source, [], maxwidth)
57+
def self.format(
58+
source,
59+
maxwidth = DEFAULT_PRINT_WIDTH,
60+
options: Formatter::Options.new
61+
)
62+
formatter = Formatter.new(source, [], maxwidth, options: options)
5963
parse(source).format(formatter)
6064

6165
formatter.flush

lib/syntax_tree/cli.rb

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,14 @@ class UnformattedError < StandardError
131131

132132
def run(item)
133133
source = item.source
134-
if source != item.handler.format(source, options.print_width)
135-
raise UnformattedError
136-
end
134+
formatted =
135+
item.handler.format(
136+
source,
137+
options.print_width,
138+
options: options.formatter_options
139+
)
140+
141+
raise UnformattedError if source != formatted
137142
rescue StandardError
138143
warn("[#{Color.yellow("warn")}] #{item.filepath}")
139144
raise
@@ -156,13 +161,23 @@ class NonIdempotentFormatError < StandardError
156161

157162
def run(item)
158163
handler = item.handler
159-
160164
warning = "[#{Color.yellow("warn")}] #{item.filepath}"
161-
formatted = handler.format(item.source, options.print_width)
162165

163-
if formatted != handler.format(formatted, options.print_width)
164-
raise NonIdempotentFormatError
165-
end
166+
formatted =
167+
handler.format(
168+
item.source,
169+
options.print_width,
170+
options: options.formatter_options
171+
)
172+
173+
double_formatted =
174+
handler.format(
175+
formatted,
176+
options.print_width,
177+
options: options.formatter_options
178+
)
179+
180+
raise NonIdempotentFormatError if formatted != double_formatted
166181
rescue StandardError
167182
warn(warning)
168183
raise
@@ -182,7 +197,9 @@ class Doc < Action
182197
def run(item)
183198
source = item.source
184199

185-
formatter = Formatter.new(source, [])
200+
formatter_options = options.formatter_options
201+
formatter = Formatter.new(source, [], options: formatter_options)
202+
186203
item.handler.parse(source).format(formatter)
187204
pp formatter.groups.first
188205
end
@@ -206,7 +223,14 @@ def run(item)
206223
# An action of the CLI that formats the input source and prints it out.
207224
class Format < Action
208225
def run(item)
209-
puts item.handler.format(item.source, options.print_width)
226+
formatted =
227+
item.handler.format(
228+
item.source,
229+
options.print_width,
230+
options: options.formatter_options
231+
)
232+
233+
puts formatted
210234
end
211235
end
212236

@@ -273,7 +297,13 @@ def run(item)
273297
start = Time.now
274298

275299
source = item.source
276-
formatted = item.handler.format(source, options.print_width)
300+
formatted =
301+
item.handler.format(
302+
source,
303+
options.print_width,
304+
options: options.formatter_options
305+
)
306+
277307
File.write(filepath, formatted) if item.writable?
278308

279309
color = source == formatted ? Color.gray(filepath) : filepath
@@ -347,14 +377,14 @@ class Options
347377
:plugins,
348378
:print_width,
349379
:scripts,
350-
:target_ruby_version
380+
:formatter_options
351381

352382
def initialize(print_width: DEFAULT_PRINT_WIDTH)
353383
@ignore_files = []
354384
@plugins = []
355385
@print_width = print_width
356386
@scripts = []
357-
@target_ruby_version = nil
387+
@formatter_options = Formatter::Options.new
358388
end
359389

360390
# TODO: This function causes a couple of side-effects that I really don't
@@ -404,8 +434,10 @@ def parser
404434
# If there is a target ruby version specified on the command line,
405435
# parse that out and use it when formatting.
406436
opts.on("--target-ruby-version=VERSION") do |version|
407-
@target_ruby_version = Gem::Version.new(version)
408-
Formatter::OPTIONS[:target_ruby_version] = @target_ruby_version
437+
@formatter_options =
438+
Formatter::Options.new(
439+
target_ruby_version: Gem::Version.new(version)
440+
)
409441
end
410442
end
411443
end

lib/syntax_tree/formatter.rb

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,47 @@ class Formatter < PrettierPrint
1010
# themselves. However, because of some history with prettier and the fact
1111
# that folks have become entrenched in their ways, we decided to provide a
1212
# small amount of configurability.
13-
#
14-
# Note that we're keeping this in a global-ish hash instead of just
15-
# overriding methods on classes so that other plugins can reference this if
16-
# necessary. For example, the RBS plugin references the quote style.
17-
OPTIONS = {
18-
quote: "\"",
19-
trailing_comma: false,
20-
target_ruby_version: Gem::Version.new(RUBY_VERSION)
21-
}
13+
class Options
14+
attr_reader :quote, :trailing_comma, :target_ruby_version
15+
16+
def initialize(
17+
quote: :default,
18+
trailing_comma: :default,
19+
target_ruby_version: :default
20+
)
21+
@quote =
22+
if quote == :default
23+
# We ship with a single quotes plugin that will define this
24+
# constant. That constant is responsible for determining the default
25+
# quote style. If it's defined, we default to single quotes,
26+
# otherwise we default to double quotes.
27+
defined?(SINGLE_QUOTES) ? "'" : "\""
28+
else
29+
quote
30+
end
31+
32+
@trailing_comma =
33+
if trailing_comma == :default
34+
# We ship with a trailing comma plugin that will define this
35+
# constant. That constant is responsible for determining the default
36+
# trailing comma value. If it's defined, then we default to true.
37+
# Otherwise we default to false.
38+
defined?(TRAILING_COMMA)
39+
else
40+
trailing_comma
41+
end
42+
43+
@target_ruby_version =
44+
if target_ruby_version == :default
45+
# Unfortunately, Gem::Version.new is not ractor-safe because it
46+
# performs global caching using a class variable. This works around
47+
# that by calling the parent method directly instead.
48+
Class.instance_method(:new).bind(Gem::Version).call(RUBY_VERSION)
49+
else
50+
target_ruby_version
51+
end
52+
end
53+
end
2254

2355
COMMENT_PRIORITY = 1
2456
HEREDOC_PRIORITY = 2
@@ -30,22 +62,16 @@ class Formatter < PrettierPrint
3062
attr_reader :quote, :trailing_comma, :target_ruby_version
3163
alias trailing_comma? trailing_comma
3264

33-
def initialize(
34-
source,
35-
*args,
36-
quote: OPTIONS[:quote],
37-
trailing_comma: OPTIONS[:trailing_comma],
38-
target_ruby_version: OPTIONS[:target_ruby_version]
39-
)
65+
def initialize(source, *args, options: Options.new)
4066
super(*args)
4167

4268
@source = source
4369
@stack = []
4470

45-
# Memoizing these values per formatter to make access faster.
46-
@quote = quote
47-
@trailing_comma = trailing_comma
48-
@target_ruby_version = target_ruby_version
71+
# Memoizing these values to make access faster.
72+
@quote = options.quote
73+
@trailing_comma = options.trailing_comma
74+
@target_ruby_version = options.target_ruby_version
4975
end
5076

5177
def self.format(source, node)

lib/syntax_tree/node.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ def call(q)
10261026
end
10271027
end
10281028

1029-
BREAKABLE_SPACE_SEPARATOR = BreakableSpaceSeparator.new
1029+
BREAKABLE_SPACE_SEPARATOR = BreakableSpaceSeparator.new.freeze
10301030

10311031
# Formats an array of multiple simple string literals into the %w syntax.
10321032
class QWordsFormatter
@@ -1759,7 +1759,7 @@ def ===(other)
17591759
module HashKeyFormatter
17601760
# Formats the keys of a hash literal using labels.
17611761
class Labels
1762-
LABEL = /\A[A-Za-z_](\w*[\w!?])?\z/
1762+
LABEL = /\A[A-Za-z_](\w*[\w!?])?\z/.freeze
17631763

17641764
def format_key(q, key)
17651765
case key
@@ -2176,7 +2176,7 @@ def call(q)
21762176

21772177
# We'll keep a single instance of this separator around for all block vars
21782178
# to cut down on allocations.
2179-
SEPARATOR = Separator.new
2179+
SEPARATOR = Separator.new.freeze
21802180

21812181
def format(q)
21822182
q.text("|")
@@ -5723,7 +5723,8 @@ def deconstruct_keys(_keys)
57235723

57245724
# This is a very specific behavior where you want to force a newline, but
57255725
# don't want to force the break parent.
5726-
SEPARATOR = PrettierPrint::Breakable.new(" ", 1, indent: false, force: true)
5726+
SEPARATOR =
5727+
PrettierPrint::Breakable.new(" ", 1, indent: false, force: true).freeze
57275728

57285729
def format(q)
57295730
q.group do
@@ -11703,7 +11704,7 @@ def call(q)
1170311704

1170411705
# We're going to keep a single instance of this separator around so we don't
1170511706
# have to allocate a new one every time we format a when clause.
11706-
SEPARATOR = Separator.new
11707+
SEPARATOR = Separator.new.freeze
1170711708

1170811709
def format(q)
1170911710
keyword = "when "
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# frozen_string_literal: true
22

3-
SyntaxTree::Formatter::OPTIONS[:quote] = "'"
3+
module SyntaxTree
4+
class Formatter
5+
SINGLE_QUOTES = true
6+
end
7+
end
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# frozen_string_literal: true
22

3-
SyntaxTree::Formatter::OPTIONS[:trailing_comma] = true
3+
module SyntaxTree
4+
class Formatter
5+
TRAILING_COMMA = true
6+
end
7+
end

syntax_tree.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
2525
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
2626
spec.require_paths = %w[lib]
2727

28-
spec.add_dependency "prettier_print", ">= 1.0.2"
28+
spec.add_dependency "prettier_print", ">= 1.1.0"
2929

3030
spec.add_development_dependency "bundler"
3131
spec.add_development_dependency "minitest"

test/cli_test.rb

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,8 @@ def test_check_print_width
6262
end
6363

6464
def test_check_target_ruby_version
65-
previous = Formatter::OPTIONS[:target_ruby_version]
66-
67-
begin
68-
result = run_cli("check", "--target-ruby-version=2.6.0")
69-
assert_includes(result.stdio, "match")
70-
ensure
71-
Formatter::OPTIONS[:target_ruby_version] = previous
72-
end
65+
result = run_cli("check", "--target-ruby-version=2.6.0")
66+
assert_includes(result.stdio, "match")
7367
end
7468

7569
def test_debug

test/plugin/single_quotes_test.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
module SyntaxTree
66
class SingleQuotesTest < Minitest::Test
7-
OPTIONS = Plugin.options("syntax_tree/plugin/single_quotes")
8-
97
def test_empty_string_literal
108
assert_format("''\n", "\"\"")
119
end
@@ -36,7 +34,8 @@ def test_label
3634
private
3735

3836
def assert_format(expected, source = expected)
39-
formatter = Formatter.new(source, [], **OPTIONS)
37+
options = Formatter::Options.new(quote: "'")
38+
formatter = Formatter.new(source, [], options: options)
4039
SyntaxTree.parse(source).format(formatter)
4140

4241
formatter.flush

test/plugin/trailing_comma_test.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
module SyntaxTree
66
class TrailingCommaTest < Minitest::Test
7-
OPTIONS = Plugin.options("syntax_tree/plugin/trailing_comma")
8-
97
def test_arg_paren_flat
108
assert_format("foo(a)\n")
119
end
@@ -82,7 +80,8 @@ def test_hash_literal_break
8280
private
8381

8482
def assert_format(expected, source = expected)
85-
formatter = Formatter.new(source, [], **OPTIONS)
83+
options = Formatter::Options.new(trailing_comma: true)
84+
formatter = Formatter.new(source, [], options: options)
8685
SyntaxTree.parse(source).format(formatter)
8786

8887
formatter.flush

0 commit comments

Comments
 (0)