module Ruby2JS
Kinda like Object.assign, except it handles properties
Note: Object.defineProperties, Object.getOwnPropertyNames, etc. technically
were not part of ES5, but were implemented by IE prior to ES6, and are the only way to implement getters and setters.
Manage a list of methods to be included or excluded. This allows fine grained control over filters.
Note care is taken to run all the filters first before camelCasing. This ensures that Ruby methods like each_pair can be mapped to JavaScript before camelcasing.
Jquery functions are either invoked using jQuery() or, more commonly, $(). The former presents no problem, the latter is not legal Ruby.
Accordingly, the first accomodation this filter provides is to map $$ to $. This works find for $$.ajax and the like, but less so for direct calls as $$(this) is also a syntax error. $$.(this) and $$[this] will work but are a bit clumsy.
So as a second accomodation, the rarely used binary one's complement unary operator (namely, ~) is usurped, and the AST is rewritten to provide the effect of this operator being of a higher precedence than method calls. Passing multiple parameters can be accomplished by using array index syntax (e.g., ~['a', self])
As a part of this rewriting, calls to getters and setters are rewritten to match jQuery's convention for getters and setters:
http://learn.jquery.com/using-jquery-core/working-with-selections/
Selected DOM properties (namely checked, disabled, readOnly, and required) can also use getter and setter syntax. Additionally, readOnly may be spelled 'readonly'.
Of course, using jQuery's style of getter and setter calls is supported, and indeed is convenient when using method chaining.
Additionally, the tilde AST rewriting can be avoided by using consecutive tildes (~~ is a common Ruby idiom for Math.floor, ~~~ will return the binary one's complement.); and the getter and setter AST rewriting can be avoided by the use of parenthesis, e.g. (~this).text.
Finally, the fourth parameter of $.post defaults to :json, allowing Ruby block syntax to be used for the success function.
Some examples of before/after conversions:
~this.val $(this).val() ~"button.continue".html = "Next Step..." $("button.continue").html("Next Step...") ~"button".readonly = false $("button").prop("readOnly", false) $$.ajax( url: "/api/getWeather", data: {zipcode: 97201}, success: proc do |data| `"#weather-temp".html = "<strong>#{data}</strong> degrees" end ) $.ajax({ url: "/api/getWeather", data: {zipcode: 97201}, success: function(data) { $("#weather-temp").html("<strong>" + data + "</strong> degrees"); } })
Convert Wunderbar syntax to JSX
Experimental secure random support
convert a JSX expression into wunderbar statements
Once the syntax is converted to pure Ruby statements, it can then be converted into either React or Vue rendering instructions.
Instances of this class keep track of both classes and modules that we have seen before, as well as the methods and properties that are defined in each.
Use cases this enables:
* detection of "open" classes and modules, i.e., redefining a class or module that was previously declared in order to add or modify methods or properties. * knowing when to prefix method or property access with `this.` and when to add `.bind(this)` for methods and properties that were defined outside of this class.
Public Class Methods
# File lib/ruby2js/execjs.rb, line 5 def self.compile(source, options={}) ExecJS.compile(convert(source, options).to_s) end
# File lib/ruby2js.rb, line 209 def self.convert(source, options={}) options = options.dup options[:eslevel] ||= @@eslevel_default options[:strict] = @@strict_default if options[:strict] == nil options[:module] ||= @@module_default || :esm if Proc === source file,line = source.source_location source = IO.read(file) ast, comments = parse(source) comments = Parser::Source::Comment.associate(ast, comments) if ast ast = find_block( ast, line ) options[:file] ||= file elsif Parser::AST::Node === source ast, comments = source, {} source = ast.loc.expression.source_buffer.source else ast, comments = parse( source, options[:file] ) comments = ast ? Parser::Source::Comment.associate(ast, comments) : {} end namespace = Namespace.new filters = (options[:filters] || Filter::DEFAULTS) unless filters.empty? filters.dup.each do |filter| filters = filter.reorder(filters) if filter.respond_to? :reorder end filter = Filter::Processor filters.reverse.each do |mod| filter = Class.new(filter) {include mod} end filter = filter.new(comments) filter.options = options filter.namespace = namespace ast = filter.process(ast) unless filter.prepend_list.empty? prepend = filter.prepend_list.sort_by {|ast| ast.type == :import ? 0 : 1} prepend.reject! {|ast| ast.type == :import} if filter.disable_autoimports ast = Parser::AST::Node.new(:begin, [*prepend, ast]) end end ruby2js = Ruby2JS::Converter.new(ast, comments) ruby2js.binding = options[:binding] ruby2js.ivars = options[:ivars] ruby2js.eslevel = options[:eslevel] ruby2js.strict = options[:strict] ruby2js.comparison = options[:comparison] || :equality ruby2js.or = options[:or] || :logical ruby2js.module_type = options[:module] || :esm ruby2js.underscored_private = (options[:eslevel] < 2022) || options[:underscored_private] ruby2js.namespace = namespace if ruby2js.binding and not ruby2js.ivars ruby2js.ivars = ruby2js.binding.eval \ 'Hash[instance_variables.map {|var| [var, instance_variable_get(var)]}]' elsif options[:scope] and not ruby2js.ivars scope = options.delete(:scope) ruby2js.ivars = Hash[scope.instance_variables.map {|var| [var, scope.instance_variable_get(var)]}] end ruby2js.width = options[:width] if options[:width] ruby2js.enable_vertical_whitespace if source.include? "\n" ruby2js.convert ruby2js.timestamp options[:file] ruby2js.file_name = options[:file] || ast&.loc&.expression&.source_buffer&.name || '' ruby2js end
# File lib/ruby2js.rb, line 28 def self.eslevel_default @@eslevel_default end
# File lib/ruby2js.rb, line 32 def self.eslevel_default=(level) @@eslevel_default = level end
# File lib/ruby2js/execjs.rb, line 9 def self.eval(source, options={}) ExecJS.eval(convert(source, options.merge(strict: false)).to_s) end
# File lib/ruby2js/execjs.rb, line 13 def self.exec(source, options={}) ExecJS.exec(convert(source, options).to_s) end
# File lib/ruby2js.rb, line 307 def self.find_block(ast, line) if ast.type == :block and ast.loc.expression.line == line return ast.children.last end ast.children.each do |child| if Parser::AST::Node === child block = find_block child, line return block if block end end nil end
# File lib/ruby2js/jsx.rb, line 8 def self.jsx2_rb(string) JsxParser.new(string.chars.each).parse.join("\n") end
# File lib/ruby2js.rb, line 44 def self.module_default @@module_default end
# File lib/ruby2js.rb, line 48 def self.module_default=(module_type) @@module_default = module_type end
# File lib/ruby2js.rb, line 291 def self.parse(source, file=nil, line=1) buffer = Parser::Source::Buffer.new(file, line) buffer.source = source.encode('UTF-8') parser = Parser::CurrentRuby.new parser.diagnostics.all_errors_are_fatal = true parser.diagnostics.consumer = lambda {|diagnostic| nil} parser.builder.emit_file_line_as_literals = false parser.parse_with_comments(buffer) rescue Parser::SyntaxError => e split = source[0..e.diagnostic.location.begin_pos].split("\n") line, col = split.length, split.last.length message = "line #{line}, column #{col}: #{e.diagnostic.message}" message += "\n in file #{file}" if file raise Ruby2JS::SyntaxError.new(message, e.diagnostic) end
# File lib/ruby2js.rb, line 36 def self.strict_default @@strict_default end
# File lib/ruby2js.rb, line 40 def self.strict_default=(level) @@strict_default = level end