ruby2js 3.5.0 → 3.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|