ruby2js 3.5.0 → 3.6.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/README.md +12 -657
- data/lib/ruby2js.rb +30 -2
- data/lib/ruby2js/converter.rb +7 -5
- data/lib/ruby2js/converter/class2.rb +63 -20
- data/lib/ruby2js/converter/def.rb +1 -1
- data/lib/ruby2js/converter/import.rb +17 -1
- data/lib/ruby2js/converter/ivar.rb +10 -2
- data/lib/ruby2js/converter/literal.rb +14 -2
- data/lib/ruby2js/converter/module.rb +5 -0
- data/lib/ruby2js/converter/send.rb +4 -3
- data/lib/ruby2js/converter/xstr.rb +1 -1
- data/lib/ruby2js/filter/active_functions.rb +43 -0
- data/lib/ruby2js/filter/camelCase.rb +4 -3
- data/lib/ruby2js/filter/esm.rb +96 -4
- data/lib/ruby2js/filter/functions.rb +23 -3
- data/lib/ruby2js/filter/node.rb +95 -74
- data/lib/ruby2js/filter/nokogiri.rb +15 -41
- data/lib/ruby2js/filter/return.rb +2 -0
- data/lib/ruby2js/filter/securerandom.rb +33 -0
- data/lib/ruby2js/filter/vue.rb +9 -0
- data/lib/ruby2js/rails.rb +15 -9
- data/lib/ruby2js/serializer.rb +3 -1
- data/lib/ruby2js/version.rb +2 -2
- data/ruby2js.gemspec +1 -1
- metadata +9 -7
- data/lib/ruby2js/filter/esm_migration.rb +0 -72
data/lib/ruby2js.rb
CHANGED
@@ -17,6 +17,7 @@ module Ruby2JS
|
|
17
17
|
|
18
18
|
@@eslevel_default = 2009 # ecmascript 5
|
19
19
|
@@strict_default = false
|
20
|
+
@@module_default = nil
|
20
21
|
|
21
22
|
def self.eslevel_default
|
22
23
|
@@eslevel_default
|
@@ -34,6 +35,14 @@ module Ruby2JS
|
|
34
35
|
@@strict_default = level
|
35
36
|
end
|
36
37
|
|
38
|
+
def self.module_default
|
39
|
+
@@module_default
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.module_default=(module_type)
|
43
|
+
@@module_default = module_type
|
44
|
+
end
|
45
|
+
|
37
46
|
module Filter
|
38
47
|
DEFAULTS = []
|
39
48
|
|
@@ -52,11 +61,19 @@ module Ruby2JS
|
|
52
61
|
include Ruby2JS::Filter
|
53
62
|
BINARY_OPERATORS = Converter::OPERATORS[2..-1].flatten
|
54
63
|
|
64
|
+
attr_accessor :prepend_list, :disable_autoimports
|
65
|
+
|
55
66
|
def initialize(comments)
|
56
67
|
@comments = comments
|
68
|
+
|
69
|
+
# check if magic comment is present:
|
70
|
+
first_comment = @comments.values.first&.map(&:text)&.first
|
71
|
+
@disable_autoimports = first_comment&.include?(" autoimports: false")
|
72
|
+
@disable_autoexports = first_comment&.include?(" autoexports: false")
|
73
|
+
|
57
74
|
@ast = nil
|
58
75
|
@exclude_methods = []
|
59
|
-
@
|
76
|
+
@prepend_list = Set.new
|
60
77
|
end
|
61
78
|
|
62
79
|
def options=(options)
|
@@ -129,6 +146,7 @@ module Ruby2JS
|
|
129
146
|
def on_method(node); on_send(node); end
|
130
147
|
def on_prop(node); on_array(node); end
|
131
148
|
def on_prototype(node); on_begin(node); end
|
149
|
+
def on_send!(node); on_send(node); end
|
132
150
|
def on_sendw(node); on_send(node); end
|
133
151
|
def on_undefined?(node); on_defined?(node); end
|
134
152
|
def on_nil(node); end
|
@@ -162,6 +180,7 @@ module Ruby2JS
|
|
162
180
|
def self.convert(source, options={})
|
163
181
|
options[:eslevel] ||= @@eslevel_default
|
164
182
|
options[:strict] = @@strict_default if options[:strict] == nil
|
183
|
+
options[:module] ||= @@module_default || :esm
|
165
184
|
|
166
185
|
if Proc === source
|
167
186
|
file,line = source.source_location
|
@@ -175,7 +194,7 @@ module Ruby2JS
|
|
175
194
|
source = ast.loc.expression.source_buffer.source
|
176
195
|
else
|
177
196
|
ast, comments = parse( source, options[:file] )
|
178
|
-
comments = Parser::Source::Comment.associate(ast, comments)
|
197
|
+
comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
|
179
198
|
end
|
180
199
|
|
181
200
|
filters = (options[:filters] || Filter::DEFAULTS)
|
@@ -193,6 +212,12 @@ module Ruby2JS
|
|
193
212
|
|
194
213
|
filter.options = options
|
195
214
|
ast = filter.process(ast)
|
215
|
+
|
216
|
+
unless filter.prepend_list.empty?
|
217
|
+
prepend = filter.prepend_list.sort_by {|ast| ast.type == :import ? 0 : 1}
|
218
|
+
prepend.reject! {|ast| ast.type == :import} if filter.disable_autoimports
|
219
|
+
ast = Parser::AST::Node.new(:begin, [*prepend, ast])
|
220
|
+
end
|
196
221
|
end
|
197
222
|
|
198
223
|
ruby2js = Ruby2JS::Converter.new(ast, comments)
|
@@ -203,6 +228,7 @@ module Ruby2JS
|
|
203
228
|
ruby2js.strict = options[:strict]
|
204
229
|
ruby2js.comparison = options[:comparison] || :equality
|
205
230
|
ruby2js.or = options[:or] || :logical
|
231
|
+
ruby2js.module_type = options[:module] || :esm
|
206
232
|
ruby2js.underscored_private = (options[:eslevel] < 2020) || options[:underscored_private]
|
207
233
|
if ruby2js.binding and not ruby2js.ivars
|
208
234
|
ruby2js.ivars = ruby2js.binding.eval \
|
@@ -221,6 +247,8 @@ module Ruby2JS
|
|
221
247
|
|
222
248
|
ruby2js.timestamp options[:file]
|
223
249
|
|
250
|
+
ruby2js.file_name = options[:file] || ast&.loc&.expression&.source_buffer&.name || ''
|
251
|
+
|
224
252
|
ruby2js
|
225
253
|
end
|
226
254
|
|
data/lib/ruby2js/converter.rb
CHANGED
@@ -3,9 +3,11 @@ require 'ruby2js/serializer'
|
|
3
3
|
module Ruby2JS
|
4
4
|
class Error < NotImplementedError
|
5
5
|
def initialize(message, ast)
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
if ast.loc
|
7
|
+
message += ' at ' + ast.loc.expression.source_buffer.name.to_s
|
8
|
+
message += ':' + ast.loc.expression.line.inspect
|
9
|
+
message += ':' + ast.loc.expression.column.to_s
|
10
|
+
end
|
9
11
|
super(message)
|
10
12
|
end
|
11
13
|
end
|
@@ -128,7 +130,7 @@ module Ruby2JS
|
|
128
130
|
Parser::AST::Node.new(type, args)
|
129
131
|
end
|
130
132
|
|
131
|
-
attr_accessor :strict, :eslevel, :comparison, :or, :underscored_private
|
133
|
+
attr_accessor :strict, :eslevel, :module_type, :comparison, :or, :underscored_private
|
132
134
|
|
133
135
|
def es2015
|
134
136
|
@eslevel >= 2015
|
@@ -246,7 +248,7 @@ module Ruby2JS
|
|
246
248
|
if ast.loc and ast.loc.expression
|
247
249
|
filename = ast.loc.expression.source_buffer.name
|
248
250
|
if filename and not filename.empty?
|
249
|
-
@timestamps[filename] ||= File.mtime(filename)
|
251
|
+
@timestamps[filename] ||= File.mtime(filename) rescue nil
|
250
252
|
end
|
251
253
|
end
|
252
254
|
|
@@ -9,11 +9,22 @@ module Ruby2JS
|
|
9
9
|
# NOTE: this is the es2015 version of class
|
10
10
|
|
11
11
|
handle :class2 do |name, inheritance, *body|
|
12
|
+
body.compact!
|
13
|
+
while body.length == 1 and body.first.type == :begin
|
14
|
+
body = body.first.children
|
15
|
+
end
|
16
|
+
|
17
|
+
proxied = body.find do |node|
|
18
|
+
node.type == :def and node.children.first == :method_missing
|
19
|
+
end
|
20
|
+
|
12
21
|
if name.type == :const and name.children.first == nil
|
13
22
|
put 'class '
|
14
23
|
parse name
|
24
|
+
put '$' if proxied
|
15
25
|
else
|
16
26
|
parse name
|
27
|
+
put '$' if proxied
|
17
28
|
put ' = class'
|
18
29
|
end
|
19
30
|
|
@@ -24,11 +35,6 @@ module Ruby2JS
|
|
24
35
|
|
25
36
|
put " {"
|
26
37
|
|
27
|
-
body.compact!
|
28
|
-
while body.length == 1 and body.first.type == :begin
|
29
|
-
body = body.first.children
|
30
|
-
end
|
31
|
-
|
32
38
|
begin
|
33
39
|
class_name, @class_name = @class_name, name
|
34
40
|
class_parent, @class_parent = @class_parent, inheritance
|
@@ -64,21 +70,21 @@ module Ruby2JS
|
|
64
70
|
walk[child] if child.is_a? Parser::AST::Node
|
65
71
|
end
|
66
72
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
73
|
+
if ast.type == :send and ast.children.first == nil
|
74
|
+
if ast.children[1] == :attr_accessor
|
75
|
+
ast.children[2..-1].each_with_index do |child_sym, index2|
|
76
|
+
ivars << :"@#{child_sym.children.first}"
|
77
|
+
end
|
78
|
+
elsif ast.children[1] == :attr_reader
|
79
|
+
ast.children[2..-1].each_with_index do |child_sym, index2|
|
80
|
+
ivars << :"@#{child_sym.children.first}"
|
81
|
+
end
|
82
|
+
elsif ast.children[1] == :attr_writer
|
83
|
+
ast.children[2..-1].each_with_index do |child_sym, index2|
|
84
|
+
ivars << :"@#{child_sym.children.first}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
82
88
|
|
83
89
|
end
|
84
90
|
walk[@ast]
|
@@ -299,6 +305,43 @@ module Ruby2JS
|
|
299
305
|
end
|
300
306
|
end
|
301
307
|
|
308
|
+
if proxied
|
309
|
+
put @sep
|
310
|
+
|
311
|
+
rename = name.updated(nil, [name.children.first, name.children.last.to_s + '$'])
|
312
|
+
|
313
|
+
if proxied.children[1].children.length == 1
|
314
|
+
# special case: if method_missing only has on argument, call it
|
315
|
+
# directly (i.e., don't pass arguments). This enables
|
316
|
+
# method_missing to return instance attributes (getters) as well
|
317
|
+
# as bound functions (methods).
|
318
|
+
forward = s(:send, s(:lvar, :obj), :method_missing, s(:lvar, :prop))
|
319
|
+
else
|
320
|
+
# normal case: return a function which, when called, will call
|
321
|
+
# method_missing with method name and arguments.
|
322
|
+
forward = s(:block, s(:send, nil, :proc), s(:args, s(:restarg, :args)),
|
323
|
+
s(:send, s(:lvar, :obj), :method_missing, s(:lvar, :prop),
|
324
|
+
s(:splat, s(:lvar, :args))))
|
325
|
+
end
|
326
|
+
|
327
|
+
proxy = s(:return, s(:send, s(:const, nil, :Proxy), :new,
|
328
|
+
s(:send, rename, :new, s(:splat, s(:lvar, :args))),
|
329
|
+
s(:hash, s(:pair, s(:sym, :get), s(:block, s(:send, nil, :proc),
|
330
|
+
s(:args, s(:arg, :obj), s(:arg, :prop)),
|
331
|
+
s(:if, s(:in?, s(:lvar, :prop), s(:lvar, :obj)),
|
332
|
+
s(:return, s(:send, s(:lvar, :obj), :[], s(:lvar, :prop))),
|
333
|
+
s(:return, forward))))))
|
334
|
+
)
|
335
|
+
|
336
|
+
if name.children.first == nil
|
337
|
+
proxy = s(:def, name.children.last, s(:args, s(:restarg, :args)), proxy)
|
338
|
+
else
|
339
|
+
proxy = s(:defs, *name.children, s(:args, s(:restarg, :args)), proxy)
|
340
|
+
end
|
341
|
+
|
342
|
+
parse proxy
|
343
|
+
end
|
344
|
+
|
302
345
|
ensure
|
303
346
|
@class_name = class_name
|
304
347
|
@class_parent = class_parent
|
@@ -131,7 +131,7 @@ module Ruby2JS
|
|
131
131
|
style = :statement
|
132
132
|
end
|
133
133
|
|
134
|
-
if args.children.length == 1 and style == :expression
|
134
|
+
if args.children.length == 1 and args.children.first.type != :restarg and style == :expression
|
135
135
|
parse args; put ' => '
|
136
136
|
else
|
137
137
|
put '('; parse args; put ') => '
|
@@ -6,7 +6,23 @@ module Ruby2JS
|
|
6
6
|
# NOTE: import is a synthetic
|
7
7
|
|
8
8
|
handle :import do |path, *args|
|
9
|
+
if module_type == :cjs
|
10
|
+
# only the subset of import syntaxes generated by filters are handled here
|
11
|
+
if Parser::AST::Node === args.first and args.first.type == :attr
|
12
|
+
return parse s(:casgn, *args.first.children,
|
13
|
+
s(:send, nil, :require, s(:str, Array(path).first))), :statement
|
14
|
+
elsif Array === args.first and args.first.length == 1
|
15
|
+
target = args.first.first
|
16
|
+
if Parser::AST::Node === target and target.type == :attr and target.children.first == nil
|
17
|
+
return parse s(:casgn, *target.children,
|
18
|
+
s(:attr, s(:send, nil, :require, s(:str, Array(path).first)), target.children.last)),
|
19
|
+
:statement
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
9
24
|
put 'import '
|
25
|
+
|
10
26
|
if args.length == 0
|
11
27
|
# import "file.css"
|
12
28
|
put path.inspect
|
@@ -66,7 +82,7 @@ module Ruby2JS
|
|
66
82
|
elsif node.respond_to?(:type) && node.children[1] == :default
|
67
83
|
put 'default '
|
68
84
|
args[0] = node.children[2]
|
69
|
-
elsif node.respond_to?(:type) && node.type
|
85
|
+
elsif node.respond_to?(:type) && [:lvasgn, :casgn].include?(node.type)
|
70
86
|
if node.children[0] == :default
|
71
87
|
put 'default '
|
72
88
|
args[0] = node.children[1]
|
@@ -16,8 +16,16 @@ module Ruby2JS
|
|
16
16
|
handle :hostvalue do |value|
|
17
17
|
case value
|
18
18
|
when Hash
|
19
|
-
parse s(:hash, *value.map {|key, hvalue|
|
20
|
-
|
19
|
+
parse s(:hash, *value.map {|key, hvalue|
|
20
|
+
case key
|
21
|
+
when String
|
22
|
+
s(:pair, s(:str, key), s(:hostvalue, hvalue))
|
23
|
+
when Symbol
|
24
|
+
s(:pair, s(:sym, key), s(:hostvalue, hvalue))
|
25
|
+
else
|
26
|
+
s(:pair, s(:hostvalue, key), s(:hostvalue, hvalue))
|
27
|
+
end
|
28
|
+
})
|
21
29
|
when Array
|
22
30
|
parse s(:array, *value.map {|hvalue| s(:hostvalue, hvalue)})
|
23
31
|
when String
|
@@ -5,12 +5,24 @@ module Ruby2JS
|
|
5
5
|
# (float 1.1)
|
6
6
|
# (str "1"))
|
7
7
|
|
8
|
-
handle :
|
8
|
+
handle :str do |value|
|
9
9
|
put value.inspect
|
10
10
|
end
|
11
11
|
|
12
|
+
handle :int, :float do |value|
|
13
|
+
put number_format(value)
|
14
|
+
end
|
15
|
+
|
12
16
|
handle :octal do |value|
|
13
|
-
put '0' + value.to_s(8)
|
17
|
+
put '0' + number_format(value.to_s(8))
|
18
|
+
end
|
19
|
+
|
20
|
+
def number_format(number)
|
21
|
+
return number.to_s unless es2021
|
22
|
+
parts = number.to_s.split('.')
|
23
|
+
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1_")
|
24
|
+
parts[1].gsub!(/(\d\d\d)(?=\d)/, "\\1_") if parts[1]
|
25
|
+
parts.join('.')
|
14
26
|
end
|
15
27
|
end
|
16
28
|
end
|
@@ -9,11 +9,12 @@ module Ruby2JS
|
|
9
9
|
# (sendw nil :puts
|
10
10
|
# (int 1))
|
11
11
|
|
12
|
-
# Note: attr, sendw, and await are only generated by filters. Attr forces
|
12
|
+
# Note: attr, sendw, send!, and await are only generated by filters. Attr forces
|
13
13
|
# interpretation as an attribute vs a function call with zero parameters.
|
14
|
+
# send! forces interpretation as a method call even with zero parameters.
|
14
15
|
# Sendw forces parameters to be placed on separate lines.
|
15
16
|
|
16
|
-
handle :send, :sendw, :await, :attr, :call do |receiver, method, *args|
|
17
|
+
handle :send, :sendw, :send!, :await, :attr, :call do |receiver, method, *args|
|
17
18
|
ast = @ast
|
18
19
|
|
19
20
|
if \
|
@@ -280,7 +281,7 @@ module Ruby2JS
|
|
280
281
|
else
|
281
282
|
put 'await ' if @ast.type == :await
|
282
283
|
|
283
|
-
if not ast.is_method?
|
284
|
+
if not ast.is_method? and ast.type != :send!
|
284
285
|
if receiver
|
285
286
|
(group_receiver ? group(receiver) : parse(receiver))
|
286
287
|
put ".#{ method }"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'ruby2js'
|
2
|
+
|
3
|
+
module Ruby2JS
|
4
|
+
module Filter
|
5
|
+
module ActiveFunctions
|
6
|
+
include SEXP
|
7
|
+
|
8
|
+
def on_send(node)
|
9
|
+
target, method, *args = node.children
|
10
|
+
|
11
|
+
if es2015 and method == :blank?
|
12
|
+
create_or_update_import("blank$")
|
13
|
+
process node.updated :send, [nil, "blank$", target]
|
14
|
+
elsif es2015 and method == :present?
|
15
|
+
create_or_update_import("present$")
|
16
|
+
process node.updated :send, [nil, "present$", target]
|
17
|
+
elsif es2015 and method == :presence
|
18
|
+
create_or_update_import("presence$")
|
19
|
+
process node.updated :send, [nil, "presence$", target]
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def create_or_update_import(token)
|
28
|
+
af_import = @options[:import_from_skypack] ? "https://cdn.skypack.dev/@ruby2js/active-functions" : "@ruby2js/active-functions"
|
29
|
+
|
30
|
+
if found_node = prepend_list.find {|ast| ast.type == :import && ast.children.first == af_import}
|
31
|
+
unless found_node.children.last.find {|const| const.children.last == token}
|
32
|
+
prepend_list.delete found_node
|
33
|
+
prepend_list << s(:import, found_node.children.first, found_node.children.last.push(s(:const, nil, token)))
|
34
|
+
end
|
35
|
+
else
|
36
|
+
prepend_list << s(:import, af_import, [s(:const, nil, token)])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
DEFAULTS.push ActiveFunctions
|
42
|
+
end
|
43
|
+
end
|
@@ -9,8 +9,9 @@ module Ruby2JS
|
|
9
9
|
module CamelCase
|
10
10
|
include SEXP
|
11
11
|
|
12
|
-
|
12
|
+
ALLOWLIST = %w{
|
13
13
|
attr_accessor
|
14
|
+
method_missing
|
14
15
|
}
|
15
16
|
|
16
17
|
CAPS_EXCEPTIONS = {
|
@@ -37,7 +38,7 @@ module Ruby2JS
|
|
37
38
|
node = super
|
38
39
|
return node unless [:send, :csend, :attr].include? node.type
|
39
40
|
|
40
|
-
if node.children[0] == nil and
|
41
|
+
if node.children[0] == nil and ALLOWLIST.include? node.children[1].to_s
|
41
42
|
node
|
42
43
|
elsif node.children[0] && [:ivar, :cvar].include?(node.children[0].type)
|
43
44
|
S(node.type, s(node.children[0].type, camelCase(node.children[0].children[0])),
|
@@ -61,7 +62,7 @@ module Ruby2JS
|
|
61
62
|
def handle_generic_node(node, node_type)
|
62
63
|
return node if node.type != node_type
|
63
64
|
|
64
|
-
if node.children[0] =~ /_.*\w$/
|
65
|
+
if node.children[0] =~ /_.*\w$/ and !ALLOWLIST.include?(node.children[0].to_s)
|
65
66
|
S(node_type , camelCase(node.children[0]), *node.children[1..-1])
|
66
67
|
else
|
67
68
|
node
|