opal 0.7.2 → 0.8.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -1
  3. data/CHANGELOG.md +29 -0
  4. data/CONTRIBUTING.md +51 -4
  5. data/Gemfile +3 -0
  6. data/README.md +5 -5
  7. data/config.ru +1 -1
  8. data/examples/sinatra/Gemfile +1 -0
  9. data/examples/sinatra/config.ru +13 -3
  10. data/lib/mspec/opal/rake_task.rb +21 -30
  11. data/lib/mspec/opal/runner.rb +37 -0
  12. data/lib/mspec/opal/special_calls.rb +6 -0
  13. data/lib/opal/builder.rb +1 -0
  14. data/lib/opal/builder_processors.rb +5 -2
  15. data/lib/opal/cli_runners/phantom.js +10 -1
  16. data/lib/opal/compiler.rb +6 -3
  17. data/lib/opal/config.rb +48 -0
  18. data/lib/opal/nodes/call.rb +3 -2
  19. data/lib/opal/nodes/literal.rb +19 -2
  20. data/lib/opal/parser/grammar.rb +2224 -2196
  21. data/lib/opal/parser/grammar.y +25 -7
  22. data/lib/opal/parser/lexer.rb +12 -9
  23. data/lib/opal/path_reader.rb +1 -1
  24. data/lib/opal/sprockets/erb.rb +6 -20
  25. data/lib/opal/sprockets/path_reader.rb +4 -2
  26. data/lib/opal/sprockets/processor.rb +135 -80
  27. data/lib/opal/sprockets/server.rb +49 -78
  28. data/lib/opal/sprockets/source_map_header_patch.rb +41 -0
  29. data/lib/opal/sprockets/source_map_server.rb +115 -0
  30. data/lib/opal/version.rb +1 -1
  31. data/lib/tilt/opal.rb +48 -0
  32. data/opal.gemspec +1 -1
  33. data/opal/corelib/array.rb +179 -51
  34. data/opal/corelib/array/inheritance.rb +14 -0
  35. data/opal/corelib/boolean.rb +5 -0
  36. data/opal/corelib/hash.rb +1 -1
  37. data/opal/corelib/kernel.rb +660 -164
  38. data/opal/corelib/match_data.rb +44 -21
  39. data/opal/corelib/module.rb +83 -53
  40. data/opal/corelib/numeric.rb +15 -1
  41. data/opal/corelib/regexp.rb +31 -75
  42. data/opal/corelib/runtime.js +20 -8
  43. data/opal/corelib/string.rb +754 -243
  44. data/opal/corelib/string/inheritance.rb +20 -3
  45. data/opal/corelib/struct.rb +30 -6
  46. data/opal/corelib/variables.rb +2 -2
  47. data/spec/filters/bugs/array.rb +0 -39
  48. data/spec/filters/bugs/kernel.rb +10 -7
  49. data/spec/filters/bugs/module.rb +21 -0
  50. data/spec/filters/bugs/opal.rb +0 -5
  51. data/spec/filters/bugs/singleton.rb +0 -2
  52. data/spec/filters/bugs/string.rb +69 -315
  53. data/spec/filters/bugs/struct.rb +0 -16
  54. data/spec/filters/unsupported/encoding.rb +7 -0
  55. data/spec/filters/unsupported/float.rb +3 -0
  56. data/spec/filters/unsupported/integer_size.rb +52 -0
  57. data/spec/filters/unsupported/marshal.rb +4 -0
  58. data/spec/filters/unsupported/mutable_strings.rb +37 -0
  59. data/spec/filters/unsupported/private_methods.rb +11 -0
  60. data/spec/filters/unsupported/rational_numbers.rb +4 -0
  61. data/spec/filters/unsupported/regular_expressions.rb +47 -0
  62. data/spec/filters/unsupported/symbols.rb +7 -0
  63. data/spec/filters/unsupported/tainted.rb +23 -1
  64. data/spec/filters/unsupported/trusted.rb +5 -0
  65. data/spec/lib/fixtures/complex_sprockets.js.rb.erb +4 -0
  66. data/spec/lib/fixtures/jst_file.js.jst +1 -0
  67. data/spec/lib/parser/call_spec.rb +19 -0
  68. data/spec/lib/parser/def_spec.rb +6 -0
  69. data/spec/lib/sprockets/erb_spec.rb +17 -4
  70. data/spec/lib/sprockets/processor_spec.rb +50 -18
  71. data/spec/lib/sprockets/server_spec.rb +39 -11
  72. data/spec/lib/tilt/opal_spec.rb +19 -0
  73. data/spec/opal/core/kernel/format_spec.rb +10 -10
  74. data/spec/opal/core/language/predefined_spec.rb +10 -0
  75. data/spec/opal/core/object_id_spec.rb +56 -0
  76. data/spec/opal/core/runtime/bridged_classes_spec.rb +4 -4
  77. data/spec/opal/core/runtime_spec.rb +7 -0
  78. data/spec/opal/stdlib/native/native_class_spec.rb +1 -1
  79. data/spec/opal/stdlib/promise/always_spec.rb +30 -0
  80. data/spec/opal/stdlib/promise/then_spec.rb +8 -0
  81. data/spec/opal/stdlib/promise/trace_spec.rb +8 -0
  82. data/spec/rubyspecs +15 -104
  83. data/spec/spec_helper.rb +4 -0
  84. data/stdlib/native.rb +7 -18
  85. data/stdlib/nodejs/file.rb +1 -1
  86. data/stdlib/opal-parser.rb +1 -0
  87. data/stdlib/pp.rb +7 -5
  88. data/stdlib/promise.rb +53 -41
  89. data/tasks/testing.rake +8 -6
  90. metadata +28 -14
  91. data/spec/filters/bugs/match_data.rb +0 -13
  92. data/spec/filters/bugs/numeric.rb +0 -22
  93. data/spec/filters/bugs/regexp.rb +0 -9
  94. data/spec/filters/bugs/unknown.rb +0 -11
@@ -607,16 +607,12 @@ rule
607
607
  result = [s(:hash, *val[0])]
608
608
  }
609
609
 
610
- paren_args: tLPAREN2 none tRPAREN
611
- {
612
- result = []
613
- }
614
- | tLPAREN2 call_args opt_nl tRPAREN
610
+ paren_args: tLPAREN2 opt_call_args rparen
615
611
  {
616
612
  result = val[1]
617
613
  }
618
- | tLPAREN2 block_call opt_nl tRPAREN
619
- | tLPAREN2 args tCOMMA block_call opt_nl tRPAREN
614
+
615
+ rparen: opt_nl tRPAREN
620
616
 
621
617
  opt_paren_args: none
622
618
  {
@@ -624,6 +620,25 @@ rule
624
620
  }
625
621
  | paren_args
626
622
 
623
+ opt_call_args: none
624
+ {
625
+ result = []
626
+ }
627
+ | call_args
628
+ | args tCOMMA
629
+ {
630
+ result = val[0]
631
+ }
632
+ | args tCOMMA assocs tCOMMA
633
+ {
634
+ result = val[0]
635
+ result << new_hash(nil, val[2], nil)
636
+ }
637
+ | assocs tCOMMA
638
+ {
639
+ result = [new_hash(nil, val[0], nil)]
640
+ }
641
+
627
642
  call_args: command
628
643
  {
629
644
  result = [val[0]]
@@ -899,6 +914,7 @@ rule
899
914
  | kDEF fname
900
915
  {
901
916
  push_scope
917
+ lexer.lex_state = :expr_endfn
902
918
  }
903
919
  f_arglist bodystmt kEND
904
920
  {
@@ -912,6 +928,7 @@ rule
912
928
  fname
913
929
  {
914
930
  push_scope
931
+ lexer.lex_state = :expr_endfn
915
932
  }
916
933
  f_arglist bodystmt kEND
917
934
  {
@@ -1456,6 +1473,7 @@ xstring_contents: none
1456
1473
  | f_args term
1457
1474
  {
1458
1475
  result = val[0]
1476
+ lexer.lex_state = :expr_beg
1459
1477
  }
1460
1478
 
1461
1479
  kwrest_mark: tPOW
@@ -249,6 +249,8 @@ module Opal
249
249
  "\v"
250
250
  elsif scan(/a/)
251
251
  "\a"
252
+ elsif scan(/b/)
253
+ "\b"
252
254
  elsif scan(/e/)
253
255
  "\e"
254
256
  elsif scan(/s/)
@@ -258,12 +260,7 @@ module Opal
258
260
  elsif scan(/x([0-9a-fA-F]{1,2})/)
259
261
  scanner[1].to_i(16).chr
260
262
  elsif scan(/u([0-9a-zA-Z]{1,4})/)
261
- if defined?(::Encoding)
262
- scanner[1].to_i(16).chr(Encoding::UTF_8)
263
- else
264
- # FIXME: no encoding on 1.8
265
- ""
266
- end
263
+ scanner[1].to_i(16).chr(Encoding::UTF_8)
267
264
  else
268
265
  # escaped char doesnt need escaping, so just return it
269
266
  scan(/./)
@@ -620,6 +617,8 @@ module Opal
620
617
 
621
618
  if [:expr_beg, :expr_dot, :expr_mid, :expr_arg, :expr_cmdarg].include? @lex_state
622
619
  @lex_state = cmd_start ? :expr_cmdarg : :expr_arg
620
+ elsif @lex_state == :expr_fname
621
+ @lex_state = :expr_endfn
623
622
  else
624
623
  @lex_state = :expr_end
625
624
  end
@@ -877,13 +876,17 @@ module Opal
877
876
  elsif scan(/\=/)
878
877
  @lex_state = :expr_beg
879
878
  return new_op_asgn('/')
880
- elsif after_operator?
881
- @lex_state = :expr_arg
882
- elsif arg?
879
+ end
880
+
881
+ if arg?
883
882
  if !check(/\s/) && @space_seen
884
883
  self.strterm = new_strterm(STR_REGEXP, '/', '/')
885
884
  return :tREGEXP_BEG
886
885
  end
886
+ end
887
+
888
+ if after_operator?
889
+ @lex_state = :expr_arg
887
890
  else
888
891
  @lex_state = :expr_beg
889
892
  end
@@ -9,7 +9,7 @@ module Opal
9
9
  def read(path)
10
10
  full_path = expand(path)
11
11
  return nil if full_path.nil?
12
- File.read(full_path)
12
+ File.open(full_path, 'rb:UTF-8'){|f| f.read}
13
13
  end
14
14
 
15
15
  def expand(path)
@@ -1,33 +1,19 @@
1
1
  require 'tilt'
2
2
  require 'sprockets'
3
+ require 'opal/sprockets/processor'
3
4
 
4
5
  module Opal
5
6
  module ERB
6
- class Processor < Tilt::Template
7
- # vvv BOILERPLATE vvv
8
- self.default_mime_type = 'application/javascript'
9
-
10
- def self.engine_initialized?
11
- true
12
- end
13
-
14
- def self.version
15
- ::Opal::VERSION
16
- end
17
-
7
+ class Processor < ::Opal::Processor
18
8
  def initialize_engine
19
- require_template_library 'opal'
9
+ super
20
10
  require_template_library 'opal/erb'
21
11
  end
22
12
 
23
- def prepare
24
- end
25
- # ^^^ BOILERPLATE ^^^
26
-
27
-
28
13
  def evaluate(context, locals, &block)
29
- context.require_asset 'erb'
30
- Opal::ERB.compile data, context.logical_path.sub(/^templates\//, '')
14
+ compiler = Opal::ERB::Compiler.new(@data, context.logical_path.sub(/^templates\//, ''))
15
+ @data = compiler.prepared_source
16
+ super
31
17
  end
32
18
  end
33
19
  end
@@ -13,14 +13,16 @@ module Opal
13
13
  env[path, bundle: true].to_s
14
14
  else
15
15
  context.depend_on(path)
16
- File.read(expand(path))
16
+ File.open(expand(path), 'rb:UTF-8'){|f| f.read}
17
17
  end
18
18
  rescue ::Sprockets::FileNotFound
19
19
  nil
20
20
  end
21
21
 
22
22
  def expand path
23
- env.resolve(path)
23
+ env.resolve(path) or
24
+ # Sprockets 3 just returns nil for unknown paths
25
+ raise ::Sprockets::FileNotFound, path.inspect
24
26
  end
25
27
 
26
28
  def paths
@@ -1,93 +1,169 @@
1
1
  require 'set'
2
- require 'tilt'
2
+ require 'tilt/opal'
3
3
  require 'sprockets'
4
- require 'opal/version'
5
4
  require 'opal/builder'
6
5
  require 'opal/sprockets/path_reader'
6
+ require 'opal/sprockets/source_map_server'
7
7
 
8
8
  $OPAL_SOURCE_MAPS = {}
9
9
 
10
10
  module Opal
11
11
  # The Processor class is used to make ruby files (with rb or opal extensions)
12
12
  # available to any sprockets based server. Processor will then get passed any
13
- # ruby source file to build. There are some options you can override globally
14
- # which effect how certain ruby features are handled:
15
- #
16
- # * method_missing_enabled [true by default]
17
- # * arity_check_enabled [false by default]
18
- # * const_missing_enabled [true by default]
19
- # * dynamic_require_severity [:error by default]
20
- # * source_map_enabled [true by default]
21
- # * irb_enabled [false by default]
22
- # * inline_operators_enabled [false by default]
23
- #
24
- class Processor < Tilt::Template
25
- # vvv BOILERPLATE vvv
26
- self.default_mime_type = 'application/javascript'
27
-
28
- def self.engine_initialized?
29
- true
13
+ # ruby source file to build.
14
+ class Processor < TiltTemplate
15
+ # DEPRECATED:
16
+ # Support legacy accessors to default options, now moved to Opal::Config
17
+ Opal::Config.default_config.keys.each do |config_option|
18
+ define_singleton_method(config_option) { Opal::Config.config[config_option] }
19
+ define_singleton_method("#{config_option}=") { |value| Opal::Config.config[config_option] = value }
30
20
  end
31
21
 
32
- def self.version
33
- ::Opal::VERSION
34
- end
22
+ def evaluate(context, locals, &block)
23
+ return super unless context.is_a? ::Sprockets::Context
35
24
 
36
- def initialize_engine
37
- require_template_library 'opal'
38
- end
25
+ @sprockets = sprockets = context.environment
26
+
27
+ # In Sprockets 3 logical_path has an odd behavior when the filename is "index"
28
+ # thus we need to bake our own logical_path
29
+ filename = context.respond_to?(:filename) ? context.filename : context.pathname.to_s
30
+ logical_path = filename.sub(%r{^#{context.root_path}/?(.*?)#{sprockets_extnames_regexp}}, '\1')
31
+
32
+ compiler_options = self.compiler_options.merge(file: logical_path)
33
+
34
+ # Opal will be loaded immediately to as the runtime redefines some crucial
35
+ # methods such that need to be implemented as soon as possible:
36
+ #
37
+ # E.g. It forwards .toString() to .$to_s() for Opal objects including Array.
38
+ # If .$to_s() is not implemented and some other lib is loaded before
39
+ # corelib/* .toString results in an `undefined is not a function` error.
40
+ compiler_options.merge!(requirable: false) if logical_path == 'opal'
39
41
 
40
- def prepare
42
+ compiler = Compiler.new(data, compiler_options)
43
+ result = compiler.compile
44
+
45
+ process_requires(compiler.requires, context)
46
+ process_required_trees(compiler.required_trees, context)
47
+
48
+ if Opal::Config.source_map_enabled
49
+ map_contents = compiler.source_map.as_json.to_json
50
+ ::Opal::SourceMapServer.set_map_cache(sprockets, logical_path, map_contents)
51
+ end
52
+
53
+ result.to_s
41
54
  end
42
- # ^^^ BOILERPLATE ^^^
43
55
 
56
+ def self.sprockets_extnames_regexp(sprockets)
57
+ joined_extnames = sprockets.engines.keys.map { |ext| Regexp.escape(ext) }.join('|')
58
+ Regexp.new("(#{joined_extnames})*$")
59
+ end
44
60
 
45
- class << self
46
- attr_accessor :method_missing_enabled
47
- attr_accessor :arity_check_enabled
48
- attr_accessor :const_missing_enabled
49
- attr_accessor :dynamic_require_severity
50
- attr_accessor :source_map_enabled
51
- attr_accessor :irb_enabled
52
- attr_accessor :inline_operators_enabled
61
+ def sprockets_extnames_regexp
62
+ @sprockets_extnames_regexp ||= self.class.sprockets_extnames_regexp(@sprockets)
63
+ end
53
64
 
54
- attr_accessor :source_map_register
65
+ def process_requires(requires, context)
66
+ requires.each do |required|
67
+ required = required.sub(sprockets_extnames_regexp, '')
68
+ context.require_asset required unless stubbed_files.include? required
69
+ end
55
70
  end
56
71
 
57
- self.method_missing_enabled = true
58
- self.arity_check_enabled = false
59
- self.const_missing_enabled = true
60
- self.dynamic_require_severity = :error # :error, :warning or :ignore
61
- self.source_map_enabled = true
62
- self.irb_enabled = false
63
- self.inline_operators_enabled = false
72
+ # Mimics (v2) Sprockets::DirectiveProcessor#process_require_tree_directive
73
+ def process_required_trees(required_trees, context)
74
+ return if required_trees.empty?
75
+
76
+ # This is the root dir of the logical path, we need this because
77
+ # the compiler gives us the path relative to the file's logical path.
78
+ dirname = File.dirname(file).gsub(/#{Regexp.escape File.dirname(context.logical_path)}$/, '')
79
+ dirname = Pathname(dirname)
80
+
81
+ required_trees.each do |original_required_tree|
82
+ required_tree = Pathname(original_required_tree)
83
+
84
+ unless required_tree.relative?
85
+ raise ArgumentError, "require_tree argument must be a relative path: #{required_tree.inspect}"
86
+ end
87
+
88
+ required_tree = dirname.join(file, '..', required_tree)
89
+
90
+ unless required_tree.directory?
91
+ raise ArgumentError, "require_tree argument must be a directory: #{[original_required_tree, required_tree].inspect}"
92
+ end
93
+
94
+ context.depend_on required_tree.to_s
95
+
96
+ environment = context.environment
97
+
98
+ if environment.respond_to?(:each_entry)
99
+ # Sprockets 2
100
+ environment.each_entry(required_tree) do |pathname|
101
+ if pathname.to_s == file
102
+ next
103
+ elsif pathname.directory?
104
+ context.depend_on(pathname)
105
+ elsif context.asset_requirable?(pathname)
106
+ context.require_asset(pathname)
107
+ end
108
+ end
109
+ else
110
+ # Sprockets 3
111
+ processor = ::Sprockets::DirectiveProcessor.new
112
+ processor.instance_variable_set('@dirname', File.dirname(file))
113
+ processor.instance_variable_set('@environment', environment)
114
+ path = processor.__send__(:expand_relative_dirname, :require_tree, original_required_tree)
115
+ absolute_paths = environment.__send__(:stat_sorted_tree_with_dependencies, path).first.map(&:first)
116
+
117
+ absolute_paths.each do |path|
118
+ path = Pathname(path)
119
+ pathname = path.relative_path_from(dirname)
120
+
121
+ if name.to_s == file
122
+ next
123
+ elsif path.directory?
124
+ context.depend_on(path.to_s)
125
+ else
126
+ context.require_asset(pathname)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
64
132
 
65
- self.source_map_register = $OPAL_SOURCE_MAPS
133
+ def self.load_asset_code(sprockets, name)
134
+ asset = sprockets[name.sub(/(\.(js|rb|opal))*$/, '.js')]
135
+ return '' if asset.nil?
66
136
 
137
+ opal_extnames = sprockets.engines.map do |ext, engine|
138
+ ext if engine <= ::Opal::Processor
139
+ end.compact
67
140
 
68
- def evaluate(context, locals, &block)
69
- return Opal.compile data unless context.is_a? ::Sprockets::Context
141
+ module_name = -> asset { asset.logical_path.sub(/\.js$/, '') }
142
+ path_extnames = -> path { File.basename(path).scan(/\.[^.]+/) }
143
+ mark_as_loaded = -> path { "Opal.mark_as_loaded(#{path.inspect});" }
144
+ processed_by_opal = -> asset { (path_extnames[asset.pathname] & opal_extnames).any? }
70
145
 
71
- path = context.logical_path
72
- prerequired = []
146
+ non_opal_assets = ([asset]+asset.dependencies)
147
+ .select { |asset| not(processed_by_opal[asset]) }
148
+ .map { |asset| module_name[asset] }
73
149
 
74
- builder = self.class.new_builder(context)
75
- result = builder.build_str(data, path, :prerequired => prerequired)
150
+ mark_as_loaded = (['opal'] + non_opal_assets + stubbed_files.to_a)
151
+ .map { |path| mark_as_loaded[path] }
76
152
 
77
- if self.class.source_map_enabled
78
- register_source_map(context.logical_path, result.source_map.to_s)
79
- "#{result.to_s}\n//# sourceMappingURL=#{File.basename(context.logical_path)}.map\n"
80
- else
81
- result.to_s
153
+ if processed_by_opal[asset]
154
+ load_asset_code = "Opal.load(#{module_name[asset].inspect});"
82
155
  end
83
- end
84
156
 
85
- def register_source_map path, map_contents
86
- self.class.source_map_register[path] = map_contents
157
+ <<-JS
158
+ if (typeof(Opal) !== 'undefined') {
159
+ #{mark_as_loaded.join("\n")}
160
+ #{load_asset_code}
161
+ }
162
+ JS
87
163
  end
88
164
 
89
165
  def self.stubbed_files
90
- @stubbed_files ||= []
166
+ @stubbed_files ||= Set.new
91
167
  end
92
168
 
93
169
  def self.stub_file(name)
@@ -97,29 +173,8 @@ module Opal
97
173
  def stubbed_files
98
174
  self.class.stubbed_files
99
175
  end
100
-
101
- def self.new_builder(context)
102
- compiler_options = {
103
- :method_missing => method_missing_enabled,
104
- :arity_check => arity_check_enabled,
105
- :const_missing => const_missing_enabled,
106
- :dynamic_require_severity => dynamic_require_severity,
107
- :irb => irb_enabled,
108
- :inline_operators => inline_operators_enabled,
109
- }
110
-
111
- path_reader = ::Opal::Sprockets::PathReader.new(context.environment, context)
112
- return Builder.new(
113
- compiler_options: compiler_options,
114
- stubs: stubbed_files,
115
- path_reader: path_reader
116
- )
117
- end
118
176
  end
119
177
  end
120
178
 
121
- Tilt.register 'rb', Opal::Processor
122
179
  Sprockets.register_engine '.rb', Opal::Processor
123
-
124
- Tilt.register 'opal', Opal::Processor
125
180
  Sprockets.register_engine '.opal', Opal::Processor