ruby2js 3.3.5 → 3.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,6 +9,22 @@ module Ruby2JS
9
9
  handle :def, :defm, :async do |name, args, body=nil|
10
10
  body ||= s(:begin)
11
11
 
12
+ add_implicit_block = false
13
+
14
+ walk = ->(node) do
15
+ add_implicit_block = true if node.type == :yield || (node.type == :send && node.children[1] == "_implicitBlockYield")
16
+ node.children.each do |child|
17
+ walk[child] if child.is_a? Parser::AST::Node
18
+ end
19
+ end
20
+ walk[body]
21
+
22
+ if add_implicit_block
23
+ children = args.children.dup
24
+ children.push s(:optarg, "_implicitBlockYield", s(:nil))
25
+ args = s(:args, *children)
26
+ end
27
+
12
28
  vars = {}
13
29
  vars.merge! @vars unless name
14
30
  if args and !args.children.empty?
@@ -20,7 +20,7 @@ module Ruby2JS
20
20
  put '`'
21
21
  children.each do |child|
22
22
  if child.type == :str
23
- str = child.children.first.inspect[1..-2].gsub('${', '$\{')
23
+ str = child.children.first.inspect[1..-2].gsub('${', '$\{').gsub('`', '\\\`')
24
24
  if heredoc
25
25
  put! str.gsub("\\n", "\n")
26
26
  else
@@ -97,10 +97,12 @@ module Ruby2JS
97
97
  end
98
98
 
99
99
  # use fat arrow syntax if block contains a reference to 'this'
100
- if anonfn
100
+ if anonfn and @class_name
101
101
  walk = proc do |ast|
102
102
  if ast == s(:self)
103
103
  anonfn = false
104
+ elsif [:ivar, :ivasgn].include? ast.type
105
+ anonfn = false
104
106
  elsif ast.type == :send and ast.children.first == nil
105
107
  method = ast.children.last if ast.children.length == 2
106
108
  if @rbstack.any? {|rb| rb[method]} or method == :this
@@ -6,15 +6,64 @@ 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 '
10
25
 
11
- args.each_with_index do |arg, index|
12
- put ', ' unless index == 0
13
- parse arg
14
- end
26
+ if args.length == 0
27
+ # import "file.css"
28
+ put path.inspect
29
+ else
30
+ # import (x) from "file.js"
31
+ default_import = !args.first.is_a?(Array) && [:const, :send, :attr].include?(args.first.type)
32
+ args = args.first if args.first.is_a?(Array)
33
+
34
+ # handle the default name or { ConstA, Const B } portion
35
+ put "{ " unless default_import
36
+ args.each_with_index do |arg, index|
37
+ put ', ' unless index == 0
38
+ parse arg
39
+ end
40
+ put " }" unless default_import
41
+
42
+ from_kwarg_position = 0
43
+
44
+ # should there be an as clause? e.g., import React as *
45
+ if path.is_a?(Array) && !path[0].is_a?(String) && path[0].type == :pair && path[0].children[0].children[0] == :as
46
+ put " as #{path[0].children[1].children[0]}"
15
47
 
16
- put ' from '
17
- put path.inspect
48
+ # advance to the next kwarg, aka from
49
+ from_kwarg_position = 1
50
+ end
51
+
52
+ put ' from '
53
+
54
+ if path.is_a?(Array) && !path[from_kwarg_position].is_a?(String) && path[from_kwarg_position].type == :pair
55
+ # from: "str" => from "str"
56
+ if path[from_kwarg_position].children[0].children[0] == :from
57
+ put path[from_kwarg_position].children[1].children[0].inspect
58
+ else
59
+ # from is missing
60
+ put '""'
61
+ end
62
+ else
63
+ # handle a str in either an array element or directly passed in
64
+ put path.is_a?(Array) ? path[0].inspect : path.inspect
65
+ end
66
+ end
18
67
  end
19
68
 
20
69
  # (export const)
@@ -24,14 +73,49 @@ module Ruby2JS
24
73
  handle :export do |*args|
25
74
  put 'export '
26
75
 
27
- if args.first == :default
76
+ node = args.first
77
+ final_export = false
78
+
79
+ if node == :default
28
80
  put 'default '
29
81
  args.shift
82
+ elsif node.respond_to?(:type) && node.children[1] == :default
83
+ put 'default '
84
+ args[0] = node.children[2]
85
+ elsif node.respond_to?(:type) && node.type == :lvasgn
86
+ if node.children[0] == :default
87
+ put 'default '
88
+ args[0] = node.children[1]
89
+ else
90
+ put 'const '
91
+ end
92
+ elsif node.respond_to?(:type) &&
93
+ node.type == :array &&
94
+ node.children[0].respond_to?(:type) &&
95
+ (
96
+ node.children[0].type == :const ||
97
+ node.children[0].type == :send ||
98
+ (node.children[0].type == :hash && node.children[0].children[0].children[0].children[0] == :default )
99
+ )
100
+ final_export = true
101
+ put '{ '
102
+ node.children.each_with_index do |arg, index|
103
+ put ', ' unless index == 0
104
+ if arg.type == :hash && arg.children[0].children[0].children[0] == :default
105
+ put arg.children[0].children[1].children[1]
106
+ put ' as default'
107
+ else
108
+ parse arg
109
+ end
110
+ end
111
+ put ' }'
30
112
  end
31
113
 
32
- args.each_with_index do |arg, index|
33
- put ', ' unless index == 0
34
- parse arg
114
+ unless final_export
115
+ args.each_with_index do |arg, index|
116
+ put ', ' unless index == 0
117
+ parse arg
118
+ end
35
119
  end
36
120
  end
37
121
  end
@@ -6,18 +6,26 @@ module Ruby2JS
6
6
  handle :ivar do |var|
7
7
  if self.ivars and self.ivars.include? var
8
8
  parse s(:hostvalue, self.ivars[var])
9
- elsif es2020
10
- parse s(:attr, s(:self), var.to_s.sub('@', '#'))
11
- else
9
+ elsif underscored_private
12
10
  parse s(:attr, s(:self), var.to_s.sub('@', '_'))
11
+ else
12
+ parse s(:attr, s(:self), var.to_s.sub('@', '#'))
13
13
  end
14
14
  end
15
15
 
16
16
  handle :hostvalue do |value|
17
17
  case value
18
18
  when Hash
19
- parse s(:hash, *value.map {|key, hvalue| s(:pair, s(:hostvalue, key),
20
- s(:hostvalue, hvalue))})
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
@@ -7,7 +7,7 @@ module Ruby2JS
7
7
  handle :ivasgn do |var, expression=nil|
8
8
  multi_assign_declarations if @state == :statement
9
9
 
10
- put "#{ var.to_s.sub('@', 'this.' + (es2020 ? '#' : '_')) }"
10
+ put "#{ var.to_s.sub('@', 'this.' + (underscored_private ? '_' : '#')) }"
11
11
  if expression
12
12
  put " = "; parse expression
13
13
  end
@@ -89,7 +89,7 @@ module Ruby2JS
89
89
  def conditionally_equals(left, right)
90
90
  if left == right
91
91
  true
92
- elsif !left or !right or left.type != :csend or right.type != :send
92
+ elsif !left.respond_to?(:type) or !left or !right or left.type != :csend or right.type != :send
93
93
  false
94
94
  else
95
95
  conditionally_equals(left.children.first, right.children.first) &&
@@ -14,7 +14,7 @@ module Ruby2JS
14
14
 
15
15
  EXPRESSIONS = [ :array, :float, :hash, :int, :lvar, :nil, :send, :attr,
16
16
  :str, :sym, :dstr, :dsym, :cvar, :ivar, :zsuper, :super, :or, :and,
17
- :block, :const, :true, :false, :xnode ]
17
+ :block, :const, :true, :false, :xnode, :taglit, :self ]
18
18
 
19
19
  handle :autoreturn do |*statements|
20
20
  return if statements == [nil]
@@ -55,6 +55,8 @@ module Ruby2JS
55
55
  node = block.pop
56
56
  children = node.children.dup
57
57
  (1...children.length).each do |i|
58
+ next if children[i].nil? # case statements without else clause end with nil
59
+
58
60
  if children[i].type == :when
59
61
  gchildren = children[i].children.dup
60
62
  if !gchildren.empty? and EXPRESSIONS.include? gchildren.last.type
@@ -0,0 +1,13 @@
1
+ module Ruby2JS
2
+ class Converter
3
+
4
+ # (taglit
5
+ # (arg :tag)
6
+ # (dstr)
7
+
8
+ handle :taglit do |tag, *children|
9
+ put tag.children.first
10
+ parse_all(*children, join: '')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Ruby2JS
2
+ class Converter
3
+
4
+ # (yield
5
+ # (arg 'a'))
6
+
7
+ handle :yield do |*args|
8
+ put '_implicitBlockYield'
9
+ put "("; parse_all(*args, join: ', '); put ')'
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,9 @@
1
1
  require 'ruby2js'
2
2
 
3
+ # Note care is taken to run all the filters first before camelCasing.
4
+ # This ensures that Ruby methods like each_pair can be mapped to
5
+ # JavaScript before camelcasing.
6
+
3
7
  module Ruby2JS
4
8
  module Filter
5
9
  module CamelCase
@@ -9,75 +13,114 @@ module Ruby2JS
9
13
  attr_accessor
10
14
  }
11
15
 
16
+ CAPS_EXCEPTIONS = {
17
+ "innerHtml" => "innerHTML",
18
+ "innerHtml=" => "innerHTML=",
19
+ "outerHtml" => "outerHTML",
20
+ "outerHtml=" => "outerHTML=",
21
+ "encodeUri" => "encodeURI",
22
+ "encodeUriComponent" => "encodeURIComponent",
23
+ "decodeUri" => "decodeURI",
24
+ "decodeUriComponent" => "decodeURIComponent"
25
+ }
26
+
12
27
  def camelCase(symbol)
13
- symbol.to_s.gsub(/_[a-z]/) {|match| match[1].upcase}
28
+ should_symbolize = symbol.is_a?(Symbol)
29
+ symbol = symbol
30
+ .to_s
31
+ .gsub(/(?!^)_[a-z0-9]/) {|match| match[1].upcase}
32
+ .gsub(/^(.*)$/) {|match| CAPS_EXCEPTIONS[match] || match }
33
+ should_symbolize ? symbol.to_sym : symbol
14
34
  end
15
35
 
16
36
  def on_send(node)
37
+ node = super
38
+ return node unless [:send, :csend, :attr].include? node.type
39
+
17
40
  if node.children[0] == nil and WHITELIST.include? node.children[1].to_s
18
- super
19
- elsif node.children[1] =~ /_.*\w$/
20
- super S(:send , node.children[0],
41
+ node
42
+ elsif node.children[0] && [:ivar, :cvar].include?(node.children[0].type)
43
+ S(node.type, s(node.children[0].type, camelCase(node.children[0].children[0])),
44
+ camelCase(node.children[1]), *node.children[2..-1])
45
+ elsif node.children[1] =~ /_.*\w[=!?]?$/
46
+ S(node.type, node.children[0],
21
47
  camelCase(node.children[1]), *node.children[2..-1])
22
48
  else
23
- super
49
+ node
24
50
  end
25
51
  end
52
+
53
+ def on_csend(node)
54
+ on_send(node)
55
+ end
56
+
57
+ def on_attr(node)
58
+ on_send(node)
59
+ end
60
+
61
+ def handle_generic_node(node, node_type)
62
+ return node if node.type != node_type
26
63
 
27
- def on_def(node)
28
64
  if node.children[0] =~ /_.*\w$/
29
- super S(:def , camelCase(node.children[0]), *node.children[1..-1])
65
+ S(node_type , camelCase(node.children[0]), *node.children[1..-1])
30
66
  else
31
- super
67
+ node
32
68
  end
33
69
  end
34
70
 
71
+ def on_def(node)
72
+ handle_generic_node(super, :def)
73
+ end
74
+
35
75
  def on_optarg(node)
36
- if node.children[0] =~ /_.*\w$/
37
- super S(:optarg , camelCase(node.children[0]), *node.children[1..-1])
38
- else
39
- super
40
- end
76
+ handle_generic_node(super, :optarg)
77
+ end
78
+
79
+ def on_kwoptarg(node)
80
+ handle_generic_node(super, :kwoptarg)
41
81
  end
42
82
 
43
83
  def on_lvar(node)
44
- if node.children[0] =~ /_.*\w$/
45
- super S(:lvar , camelCase(node.children[0]), *node.children[1..-1])
46
- else
47
- super
48
- end
84
+ handle_generic_node(super, :lvar)
85
+ end
86
+
87
+ def on_ivar(node)
88
+ handle_generic_node(super, :ivar)
89
+ end
90
+
91
+ def on_cvar(node)
92
+ handle_generic_node(super, :cvar)
49
93
  end
50
94
 
51
95
  def on_arg(node)
52
- if node.children[0] =~ /_.*\w$/
53
- super S(:arg , camelCase(node.children[0]), *node.children[1..-1])
54
- else
55
- super
56
- end
96
+ handle_generic_node(super, :arg)
57
97
  end
58
98
 
59
99
  def on_lvasgn(node)
60
- if node.children[0] =~ /_.*\w$/
61
- super S(:lvasgn , camelCase(node.children[0]), *node.children[1..-1])
62
- else
63
- super
64
- end
100
+ handle_generic_node(super, :lvasgn)
101
+ end
102
+
103
+ def on_ivasgn(node)
104
+ handle_generic_node(super, :ivasgn)
105
+ end
106
+
107
+ def on_cvasgn(node)
108
+ handle_generic_node(super, :cvasgn)
65
109
  end
66
110
 
67
111
  def on_sym(node)
68
- if node.children[0] =~ /_.*\w$/
69
- super S(:sym , camelCase(node.children[0]), *node.children[1..-1])
70
- else
71
- super
72
- end
112
+ handle_generic_node(super, :sym)
73
113
  end
74
114
 
75
115
  def on_defs(node)
116
+ node = super
117
+ return node if node.type != :defs
118
+
76
119
  if node.children[1] =~ /_.*\w$/
77
- super S(:defs , node.children[0],
120
+ S(:defs , node.children[0],
78
121
  camelCase(node.children[1]), *node.children[2..-1])
79
122
  else
80
- super
123
+ node
81
124
  end
82
125
  end
83
126
  end
@@ -1,69 +1,98 @@
1
1
  require 'ruby2js'
2
2
 
3
+ Ruby2JS.module_default = :esm
4
+
3
5
  module Ruby2JS
4
6
  module Filter
5
7
  module ESM
6
8
  include SEXP
7
9
 
8
10
  def initialize(*args)
9
- @esm_include = nil
10
11
  super
12
+ @esm = true # signal for other filters
13
+ @esm_imports = nil
14
+ end
15
+
16
+ def options=(options)
17
+ super
18
+ @esm_autoimports = options[:autoimports]
19
+ return unless @esm_autoimports
11
20
  end
12
21
 
13
22
  def process(node)
14
- return super if @esm_include
15
- @esm_include = Set.new
16
- @esm_exclude = Set.new
17
- @esm_export = nil
23
+ return super if @esm_imports or not @esm_autoimports
24
+ @esm_imports = Set.new
18
25
  result = super
19
26
 
20
- esm_walk(result)
21
-
22
- inventory = (@esm_include - @esm_exclude).to_a.sort
23
-
24
- if inventory.empty? and not @esm_export
27
+ if @esm_imports.empty?
25
28
  result
26
29
  else
27
- list = inventory.map do |name|
28
- if name == "React" and defined? Ruby2JS::Filter::React
29
- s(:import, "#{name.downcase}", s(:const, nil, name))
30
- elsif not %w(JSON Object).include? name
31
- s(:import, "./#{name.downcase}.js", s(:const, nil, name))
32
- end
33
- end
30
+ s(:begin, *@esm_imports.to_a.map {|token|
31
+ s(:import, @esm_autoimports[token], s(:const, nil, token))
32
+ }, result)
33
+ end
34
+ end
34
35
 
35
- list.push result
36
+ def on_send(node)
37
+ target, method, *args = node.children
38
+ return super unless target.nil?
36
39
 
37
- if @esm_export
38
- list.push s(:export, :default, s(:const, nil, @esm_export))
40
+ if method == :import
41
+ # don't do the conversion if the word import is followed by a paren
42
+ if node.loc.respond_to? :selector
43
+ selector = node.loc.selector
44
+ if selector and selector.source_buffer
45
+ return super if selector.source_buffer.source[selector.end_pos] == '('
46
+ end
39
47
  end
40
48
 
41
- s(:begin, *list.compact)
42
- end
43
- end
49
+ if args[0].type == :str
50
+ # import "file.css"
51
+ # => import "file.css"
52
+ s(:import, args[0].children[0])
53
+ elsif args.length == 1 and \
54
+ args[0].type == :send and \
55
+ args[0].children[0].nil? and \
56
+ args[0].children[2].type == :send and \
57
+ args[0].children[2].children[0].nil? and \
58
+ args[0].children[2].children[1] == :from and \
59
+ args[0].children[2].children[2].type == :str
60
+ # import name from "file.js"
61
+ # => import name from "file.js"
62
+ s(:import,
63
+ [args[0].children[2].children[2].children[0]],
64
+ process(s(:attr, nil, args[0].children[1])))
44
65
 
45
- # gather constants
46
- def esm_walk(node)
47
- # extract ivars and cvars
48
- if node.type == :const and node.children.first == nil
49
- @esm_include << node.children.last.to_s
50
- elsif node.type == :xnode
51
- name = node.children.first
52
- @esm_include << name unless name.empty? or name =~ /^[a-z]/
53
- elsif node.type == :casgn and node.children.first == nil
54
- @esm_exclude << node.children[1].to_s
55
- elsif node.type == :class and node.children.first.type == :const
56
- if node.children.first.children.first == nil
57
- name = node.children.first.children.last.to_s
58
- @esm_exclude << name
59
- @esm_export ||= name
66
+ else
67
+ # import Stuff, "file.js"
68
+ # => import Stuff from "file.js"
69
+ # import Stuff, from: "file.js"
70
+ # => import Stuff from "file.js"
71
+ # import Stuff, as: "*", from: "file.js"
72
+ # => import Stuff as * from "file.js"
73
+ # import [ Some, Stuff ], from: "file.js"
74
+ # => import { Some, Stuff } from "file.js"
75
+ imports = (args[0].type == :const || args[0].type == :send) ?
76
+ process(args[0]) :
77
+ process_all(args[0].children)
78
+ s(:import, args[1].children, imports) unless args[1].nil?
60
79
  end
80
+ elsif method == :export
81
+ s(:export, *process_all(args))
82
+ elsif @esm_imports and args.length == 0 and @esm_autoimports[method]
83
+ @esm_imports.add(method)
84
+ super
85
+ else
86
+ super
61
87
  end
88
+ end
62
89
 
63
- # recurse
64
- node.children.each do |child|
65
- esm_walk(child) if Parser::AST::Node === child
90
+ def on_const(node)
91
+ return super unless @esm_autoimports
92
+ if node.children.first == nil and @esm_autoimports[node.children.last]
93
+ @esm_imports.add(node.children.last)
66
94
  end
95
+ super
67
96
  end
68
97
  end
69
98