Loading README.md +3 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,9 @@ Thanks to [Ondřej Jirman](https://github.com/megous), Katherine Chu, [Rob Dougan](https://github.com/rdougan), [Dave Thompson](https://github.com/limscoder), [burnnat](https://github.com/burnnat), [vjetteam](https://github.com/vjetteam), [Chris Westbrook](https://github.com/cnstaging), and many-many others who reported bugs, submitted patches, and provided a lot of useful input. Loading benchmark/lex.rb→benchmark/parse.rb +4 −4 Original line number Diff line number Diff line # Script for benchmarking the lexer. # Script for benchmarking JavaScript parser. # # Takes bunch of filenames as arguments and runs them all through lexer. # Takes bunch of filenames as arguments and runs them all through JsParser. # $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib" require 'jsduck/lexer' require 'jsduck/js_parser' ARGV.each do |fname| JsDuck::Lexer.new(IO.read(fname)) JsDuck::JsParser.new(IO.read(fname)).parse end benchmark/serialize.rb 0 → 100644 +14 −0 Original line number Diff line number Diff line # Script for benchmarking the Serializer. # # Takes bunch of filenames as arguments, runs them all through esprima # parser and serializes the resulting syntax trees. # $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib" require 'jsduck/esprima' require 'jsduck/serializer' ARGV.each do |fname| ast = JsDuck::Esprima.instance.parse(IO.read(fname)) JsDuck::Serializer.new.to_s(ast) end bin/compare +199 −37 Original line number Diff line number Diff line #!/usr/bin/env ruby # Compare .json files of different ExtJS versions # Produces diff of two JSDuck exports. # For running when gem not installed $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib" require "rubygems" require "cgi" require "optparse" require "jsduck/json_duck" options = { :title => "Comparison of Ext 4.0.7 and Ext 4.1.1", } input_files = OptionParser.new do |opts| opts.banner = "Produces diff of two JSDuck exports.\n\n" + "Usage: compare [options] old/classes/ new/classes/ output.html\n\n" opts.on("--ignore=FILE", "A file listing members to be ignored") do |file| options[:ignore] = file end opts.on("--type=NAME", "Only produce diff of cfg, property, method or event.") do |name| options[:type] = name end opts.on("--title=TEXT", "Title for the generated HTML page.", "Defaults to: 'Comparison of Ext 4.0.7 and Ext 4.1.1'") do |text| options[:title] = text end opts.on("-h", "--help", "Show this help message") do puts opts exit end end.parse! if input_files.length == 3 options[:old_classes] = input_files[0] options[:new_classes] = input_files[1] options[:out_file] = input_files[2] else puts "You must specify exactly 3 filenames: old/classes/ new/classes/ output.html" exit 1 end def read_class(filename) JsDuck::JsonDuck.read(filename) end # loops through all members of class def each_member(cls) ["members", "statics"].each do |category| cls[category].each_pair do |k, members| members.each do |m| yield m end end end end def discard_docs(cls) each_member(cls) do |m| m["doc"] = nil m["params"] = nil end cls end def read_all_classes(dir, ignore_private=false) map = {} Dir[dir+"/*.json"].each do |filename| print "." STDOUT.flush cls = read_class(filename) cls = discard_docs(read_class(filename)) unless ignore_private && cls["private"] map[cls["name"]] = cls cls["alternateClassNames"].each do |name| Loading @@ -40,24 +98,86 @@ def normal_public_member?(m) !m["private"] && !m["meta"]["protected"] && !m["meta"]["deprecated"] && !m["meta"]["removed"] end # Gathers class members that are in cls1, but are missing in cls2 # Ignoring members listed in ignored_members hash. def compare_classes(cls1, cls2, ignored_members) def unify_default(v) (v || "undefined").gsub(/"/, "'").sub(/^Ext\.baseCSSPrefix \+ '/, "'x-") end def visibility(m) if m["private"] "private" elsif m["meta"]["protected"] "protected" else "public" end end # Finds equivalent member to given member from class. # Returns the member found or nil. def find_member(cls, member) cls[member["meta"]["static"] ? "statics" : "members"][member["tagname"]].find do |m| member["name"] == m["name"] end end # Compares members of cls1 to cls2. # Returns diff array of added, removed and changed members. def compare_classes(cls1, cls2) diff = [] cls1["members"].each_pair do |group_name, group_items| group_items.find_all {|m1| normal_public_member?(m1) && m1["owner"] == cls1["name"] }.each do |m1| match = cls2["members"][group_name].find do |m2| m2["name"] == m1["name"] && !m2["meta"]["protected"] && !m2["private"] checked_members = {} each_member(cls1) do |m1| next unless normal_public_member?(m1) && m1["owner"] == cls1["name"] checked_members[m1["id"]] = true m2 = find_member(cls2, m1) if m2 changes = [] if m1["type"] != m2["type"] changes << { :what => "type", :a => m1["type"], :b => m2["type"], } end if unify_default(m1["default"]) != unify_default(m2["default"]) changes << { :what => "default", :a => unify_default(m1["default"]), :b => unify_default(m2["default"]), } end if visibility(m1) != visibility(m2) changes << { :what => "visibility", :a => visibility(m1), :b => visibility(m2), } end if !match && m1["name"] != "constructor" && m1["name"] != "" && !ignored_members[cls1["name"]+"#"+m1["name"]] if changes.length > 0 diff << { :type => m1["tagname"], :name => m1["name"], :what => "changed", :changes => changes, } end elsif !m2 && m1["name"] != "constructor" && m1["name"] != "" other = nil ["cfg", "property", "method", "event"].each do |g| other = other || cls2["members"][g].find {|m2| m2["name"] == m1["name"] } other = other || cls2["statics"][g].find {|m2| m2["name"] == m1["name"] } end diff << { :type => group_name, :type => m1["tagname"], :name => m1["name"], :what => "removed", :other => other ? { :type => other["tagname"], :static => other["meta"]["static"], Loading @@ -67,15 +187,47 @@ def compare_classes(cls1, cls2, ignored_members) } end end each_member(cls2) do |m2| next unless normal_public_member?(m2) && m2["owner"] == cls2["name"] && !checked_members[m2["id"]] m1 = find_member(cls1, m2) if m1 changes = [] if visibility(m1) != visibility(m2) changes << { :what => "visibility", :a => visibility(m1), :b => visibility(m2), } end if changes.length > 0 diff << { :type => m2["tagname"], :name => m2["name"], :what => "changed", :changes => changes, } end elsif !m1 && m2["name"] != "constructor" && m2["name"] != "" diff << { :type => m2["tagname"], :name => m2["name"], :what => "added", } end end diff end old_classes = read_all_classes(ARGV[0], :ignore_private) new_classes = read_all_classes(ARGV[1]) out_file = ARGV[2] ignored_members = ARGV[3] ? read_ignored_members(ARGV[3]) : {} old_classes = read_all_classes(options[:old_classes], :ignore_private) new_classes = read_all_classes(options[:new_classes]) ignored_members = options[:ignore] ? read_ignored_members(options[:ignore]) : {} # Explicit remapping of classes remap = { Loading @@ -101,16 +253,23 @@ old_classes.each_pair do |name, cls| :name => cls["name"], :found => !!new_cls || ignored_members[name], :new_name => new_cls && new_cls["name"], :diff => new_cls ? compare_classes(cls, new_cls, ignored_members) : [], :diff => new_cls ? compare_classes(cls, new_cls) : [], } end end # Throw away ignored members and everything except configs diff_data.each do |cls| cls[:diff] = cls[:diff].find_all do |m| !ignored_members[cls[:name]+"#"+m[:name]] && (!options[:type] || m[:type] == options[:type]) end end # Choose title based on filename if out_file =~ /touch/ if options[:out_file] =~ /touch/ title = "Comparison of Touch 1.1.1 and Touch 2.0.0" else title = "Comparison of Ext 4.0.7 and Ext 4.1.0" title = "Comparison of Ext 4.0.7 and Ext 4.1.1" end # do HTML output Loading @@ -119,7 +278,7 @@ html << <<-EOHTML <!DOCTYPE html> <html> <head> <title>#{title}</title> <title>#{options[:title]}</title> <style type="text/css"> body { font-family: Georgia, serif; } li h2 { font-size: medium; font-weight: normal; } Loading @@ -136,7 +295,7 @@ $(function() { </script> </head> <body> <h1>#{title}</h1> <h1>#{options[:title]}</h1> EOHTML html << "<ul>" Loading @@ -146,20 +305,24 @@ diff_data.each do |cls| html << "<li>" if cls[:found] new_name = (cls[:new_name] == cls[:name] ? "" : " --> " + cls[:new_name]) diff_count = cls[:diff].length > 0 ? "(#{cls[:diff].length} missing members)" : "" diff_count = cls[:diff].length > 0 ? "(#{cls[:diff].length} changes)" : "" link = cls[:diff].length > 0 ? "<a href='#expand'>#{cls[:name]}</a>" : cls[:name] html << "<h2>#{link} #{new_name} #{diff_count}</h2>" if cls[:diff].length > 0 html << "<ul>" cls[:diff].each do |m| html << "<li>" html << m[:type] + " " + m[:name] html << m[:what] + " " + m[:type] + " " + m[:name] if m[:other] o = m[:other] stat = o[:static] ? 'static' : '' priv = o[:private] ? 'private' : '' prot = o[:protected] ? 'protected' : '' html << " (found #{stat} #{priv} #{prot} #{o[:type]} with the same name)" elsif m[:changes] m[:changes].each do |c| html << " (#{c[:what]} changed from #{CGI.escapeHTML(c[:a])} to #{CGI.escapeHTML(c[:b])})" end end html << "</li>" end Loading @@ -178,12 +341,11 @@ dd = diff_data html << "<p>" + dd.find_all {|c| !c[:found] }.length.to_s + " classes not found</p>" html << "<p>" + dd.find_all {|c| c[:found] && c[:name] == c[:new_name] }.length.to_s + " classes with same name</p>" html << "<p>" + dd.find_all {|c| c[:found] && c[:name] != c[:new_name] }.length.to_s + " renamed classes</p>" html << "<p>" + dd.find_all {|c| c[:diff].length > 0 }.length.to_s + " classes with missing members</p>" html << "<p>" + dd.map {|c| c[:diff].length }.inject {|sum,x| sum + x }.to_s + " missing members</p>" html << "<p>" + dd.map {|c| c[:diff].find_all {|m| !m[:other]}.length }.inject {|sum,x| sum + x }.to_s + " completely missing members</p>" html << "<p>" + dd.find_all {|c| c[:diff].length > 0 }.length.to_s + " classes with changed members</p>" html << "<p>" + dd.map {|c| c[:diff].length }.inject {|sum,x| sum + x }.to_s + " changed members</p>" html << "</body>" html << "</html>" File.open(out_file, 'w') {|f| f.write(html.join("\n")) } File.open(options[:out_file], 'w') {|f| f.write(html.join("\n")) } bin/graph 0 → 100755 +52 −0 Original line number Diff line number Diff line #!/usr/bin/env ruby # For running when gem not installed $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib" require "rubygems" require "pp" require "jsduck/json_duck" def with_each_class(dir) Dir[dir+"/*.json"].each do |filename| yield JsDuck::JsonDuck.read(filename) end end def quote(a) '"'+a+'"' end def arrow(a, b, opts="") " #{quote(a)}->#{quote(b)} #{opts};" end input_dir = ARGV[0] # Build a map that links each classname or alternate classname to its # canonical form $canonical_map = {} with_each_class(input_dir) do |cls| $canonical_map[cls["name"]] = cls["name"] cls["alternateClassNames"].each do |name| $canonical_map[name] = cls["name"] end end def canonical(name) $canonical_map[name] || name end # Print out the graph description puts 'digraph G {' puts 'rankdir=LR;' with_each_class(input_dir) do |cls| if cls["extends"] && cls["extends"] != "Object" puts arrow(canonical(cls["extends"]), cls['name'], '[style=bold,weight=10]') end if cls["mixins"] cls["mixins"].each {|mx| puts arrow(canonical(mx), cls['name'], '[weight=1,style=dashed]') } end end puts '}' Loading
README.md +3 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,9 @@ Thanks to [Ondřej Jirman](https://github.com/megous), Katherine Chu, [Rob Dougan](https://github.com/rdougan), [Dave Thompson](https://github.com/limscoder), [burnnat](https://github.com/burnnat), [vjetteam](https://github.com/vjetteam), [Chris Westbrook](https://github.com/cnstaging), and many-many others who reported bugs, submitted patches, and provided a lot of useful input. Loading
benchmark/lex.rb→benchmark/parse.rb +4 −4 Original line number Diff line number Diff line # Script for benchmarking the lexer. # Script for benchmarking JavaScript parser. # # Takes bunch of filenames as arguments and runs them all through lexer. # Takes bunch of filenames as arguments and runs them all through JsParser. # $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib" require 'jsduck/lexer' require 'jsduck/js_parser' ARGV.each do |fname| JsDuck::Lexer.new(IO.read(fname)) JsDuck::JsParser.new(IO.read(fname)).parse end
benchmark/serialize.rb 0 → 100644 +14 −0 Original line number Diff line number Diff line # Script for benchmarking the Serializer. # # Takes bunch of filenames as arguments, runs them all through esprima # parser and serializes the resulting syntax trees. # $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib" require 'jsduck/esprima' require 'jsduck/serializer' ARGV.each do |fname| ast = JsDuck::Esprima.instance.parse(IO.read(fname)) JsDuck::Serializer.new.to_s(ast) end
bin/compare +199 −37 Original line number Diff line number Diff line #!/usr/bin/env ruby # Compare .json files of different ExtJS versions # Produces diff of two JSDuck exports. # For running when gem not installed $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib" require "rubygems" require "cgi" require "optparse" require "jsduck/json_duck" options = { :title => "Comparison of Ext 4.0.7 and Ext 4.1.1", } input_files = OptionParser.new do |opts| opts.banner = "Produces diff of two JSDuck exports.\n\n" + "Usage: compare [options] old/classes/ new/classes/ output.html\n\n" opts.on("--ignore=FILE", "A file listing members to be ignored") do |file| options[:ignore] = file end opts.on("--type=NAME", "Only produce diff of cfg, property, method or event.") do |name| options[:type] = name end opts.on("--title=TEXT", "Title for the generated HTML page.", "Defaults to: 'Comparison of Ext 4.0.7 and Ext 4.1.1'") do |text| options[:title] = text end opts.on("-h", "--help", "Show this help message") do puts opts exit end end.parse! if input_files.length == 3 options[:old_classes] = input_files[0] options[:new_classes] = input_files[1] options[:out_file] = input_files[2] else puts "You must specify exactly 3 filenames: old/classes/ new/classes/ output.html" exit 1 end def read_class(filename) JsDuck::JsonDuck.read(filename) end # loops through all members of class def each_member(cls) ["members", "statics"].each do |category| cls[category].each_pair do |k, members| members.each do |m| yield m end end end end def discard_docs(cls) each_member(cls) do |m| m["doc"] = nil m["params"] = nil end cls end def read_all_classes(dir, ignore_private=false) map = {} Dir[dir+"/*.json"].each do |filename| print "." STDOUT.flush cls = read_class(filename) cls = discard_docs(read_class(filename)) unless ignore_private && cls["private"] map[cls["name"]] = cls cls["alternateClassNames"].each do |name| Loading @@ -40,24 +98,86 @@ def normal_public_member?(m) !m["private"] && !m["meta"]["protected"] && !m["meta"]["deprecated"] && !m["meta"]["removed"] end # Gathers class members that are in cls1, but are missing in cls2 # Ignoring members listed in ignored_members hash. def compare_classes(cls1, cls2, ignored_members) def unify_default(v) (v || "undefined").gsub(/"/, "'").sub(/^Ext\.baseCSSPrefix \+ '/, "'x-") end def visibility(m) if m["private"] "private" elsif m["meta"]["protected"] "protected" else "public" end end # Finds equivalent member to given member from class. # Returns the member found or nil. def find_member(cls, member) cls[member["meta"]["static"] ? "statics" : "members"][member["tagname"]].find do |m| member["name"] == m["name"] end end # Compares members of cls1 to cls2. # Returns diff array of added, removed and changed members. def compare_classes(cls1, cls2) diff = [] cls1["members"].each_pair do |group_name, group_items| group_items.find_all {|m1| normal_public_member?(m1) && m1["owner"] == cls1["name"] }.each do |m1| match = cls2["members"][group_name].find do |m2| m2["name"] == m1["name"] && !m2["meta"]["protected"] && !m2["private"] checked_members = {} each_member(cls1) do |m1| next unless normal_public_member?(m1) && m1["owner"] == cls1["name"] checked_members[m1["id"]] = true m2 = find_member(cls2, m1) if m2 changes = [] if m1["type"] != m2["type"] changes << { :what => "type", :a => m1["type"], :b => m2["type"], } end if unify_default(m1["default"]) != unify_default(m2["default"]) changes << { :what => "default", :a => unify_default(m1["default"]), :b => unify_default(m2["default"]), } end if visibility(m1) != visibility(m2) changes << { :what => "visibility", :a => visibility(m1), :b => visibility(m2), } end if !match && m1["name"] != "constructor" && m1["name"] != "" && !ignored_members[cls1["name"]+"#"+m1["name"]] if changes.length > 0 diff << { :type => m1["tagname"], :name => m1["name"], :what => "changed", :changes => changes, } end elsif !m2 && m1["name"] != "constructor" && m1["name"] != "" other = nil ["cfg", "property", "method", "event"].each do |g| other = other || cls2["members"][g].find {|m2| m2["name"] == m1["name"] } other = other || cls2["statics"][g].find {|m2| m2["name"] == m1["name"] } end diff << { :type => group_name, :type => m1["tagname"], :name => m1["name"], :what => "removed", :other => other ? { :type => other["tagname"], :static => other["meta"]["static"], Loading @@ -67,15 +187,47 @@ def compare_classes(cls1, cls2, ignored_members) } end end each_member(cls2) do |m2| next unless normal_public_member?(m2) && m2["owner"] == cls2["name"] && !checked_members[m2["id"]] m1 = find_member(cls1, m2) if m1 changes = [] if visibility(m1) != visibility(m2) changes << { :what => "visibility", :a => visibility(m1), :b => visibility(m2), } end if changes.length > 0 diff << { :type => m2["tagname"], :name => m2["name"], :what => "changed", :changes => changes, } end elsif !m1 && m2["name"] != "constructor" && m2["name"] != "" diff << { :type => m2["tagname"], :name => m2["name"], :what => "added", } end end diff end old_classes = read_all_classes(ARGV[0], :ignore_private) new_classes = read_all_classes(ARGV[1]) out_file = ARGV[2] ignored_members = ARGV[3] ? read_ignored_members(ARGV[3]) : {} old_classes = read_all_classes(options[:old_classes], :ignore_private) new_classes = read_all_classes(options[:new_classes]) ignored_members = options[:ignore] ? read_ignored_members(options[:ignore]) : {} # Explicit remapping of classes remap = { Loading @@ -101,16 +253,23 @@ old_classes.each_pair do |name, cls| :name => cls["name"], :found => !!new_cls || ignored_members[name], :new_name => new_cls && new_cls["name"], :diff => new_cls ? compare_classes(cls, new_cls, ignored_members) : [], :diff => new_cls ? compare_classes(cls, new_cls) : [], } end end # Throw away ignored members and everything except configs diff_data.each do |cls| cls[:diff] = cls[:diff].find_all do |m| !ignored_members[cls[:name]+"#"+m[:name]] && (!options[:type] || m[:type] == options[:type]) end end # Choose title based on filename if out_file =~ /touch/ if options[:out_file] =~ /touch/ title = "Comparison of Touch 1.1.1 and Touch 2.0.0" else title = "Comparison of Ext 4.0.7 and Ext 4.1.0" title = "Comparison of Ext 4.0.7 and Ext 4.1.1" end # do HTML output Loading @@ -119,7 +278,7 @@ html << <<-EOHTML <!DOCTYPE html> <html> <head> <title>#{title}</title> <title>#{options[:title]}</title> <style type="text/css"> body { font-family: Georgia, serif; } li h2 { font-size: medium; font-weight: normal; } Loading @@ -136,7 +295,7 @@ $(function() { </script> </head> <body> <h1>#{title}</h1> <h1>#{options[:title]}</h1> EOHTML html << "<ul>" Loading @@ -146,20 +305,24 @@ diff_data.each do |cls| html << "<li>" if cls[:found] new_name = (cls[:new_name] == cls[:name] ? "" : " --> " + cls[:new_name]) diff_count = cls[:diff].length > 0 ? "(#{cls[:diff].length} missing members)" : "" diff_count = cls[:diff].length > 0 ? "(#{cls[:diff].length} changes)" : "" link = cls[:diff].length > 0 ? "<a href='#expand'>#{cls[:name]}</a>" : cls[:name] html << "<h2>#{link} #{new_name} #{diff_count}</h2>" if cls[:diff].length > 0 html << "<ul>" cls[:diff].each do |m| html << "<li>" html << m[:type] + " " + m[:name] html << m[:what] + " " + m[:type] + " " + m[:name] if m[:other] o = m[:other] stat = o[:static] ? 'static' : '' priv = o[:private] ? 'private' : '' prot = o[:protected] ? 'protected' : '' html << " (found #{stat} #{priv} #{prot} #{o[:type]} with the same name)" elsif m[:changes] m[:changes].each do |c| html << " (#{c[:what]} changed from #{CGI.escapeHTML(c[:a])} to #{CGI.escapeHTML(c[:b])})" end end html << "</li>" end Loading @@ -178,12 +341,11 @@ dd = diff_data html << "<p>" + dd.find_all {|c| !c[:found] }.length.to_s + " classes not found</p>" html << "<p>" + dd.find_all {|c| c[:found] && c[:name] == c[:new_name] }.length.to_s + " classes with same name</p>" html << "<p>" + dd.find_all {|c| c[:found] && c[:name] != c[:new_name] }.length.to_s + " renamed classes</p>" html << "<p>" + dd.find_all {|c| c[:diff].length > 0 }.length.to_s + " classes with missing members</p>" html << "<p>" + dd.map {|c| c[:diff].length }.inject {|sum,x| sum + x }.to_s + " missing members</p>" html << "<p>" + dd.map {|c| c[:diff].find_all {|m| !m[:other]}.length }.inject {|sum,x| sum + x }.to_s + " completely missing members</p>" html << "<p>" + dd.find_all {|c| c[:diff].length > 0 }.length.to_s + " classes with changed members</p>" html << "<p>" + dd.map {|c| c[:diff].length }.inject {|sum,x| sum + x }.to_s + " changed members</p>" html << "</body>" html << "</html>" File.open(out_file, 'w') {|f| f.write(html.join("\n")) } File.open(options[:out_file], 'w') {|f| f.write(html.join("\n")) }
bin/graph 0 → 100755 +52 −0 Original line number Diff line number Diff line #!/usr/bin/env ruby # For running when gem not installed $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib" require "rubygems" require "pp" require "jsduck/json_duck" def with_each_class(dir) Dir[dir+"/*.json"].each do |filename| yield JsDuck::JsonDuck.read(filename) end end def quote(a) '"'+a+'"' end def arrow(a, b, opts="") " #{quote(a)}->#{quote(b)} #{opts};" end input_dir = ARGV[0] # Build a map that links each classname or alternate classname to its # canonical form $canonical_map = {} with_each_class(input_dir) do |cls| $canonical_map[cls["name"]] = cls["name"] cls["alternateClassNames"].each do |name| $canonical_map[name] = cls["name"] end end def canonical(name) $canonical_map[name] || name end # Print out the graph description puts 'digraph G {' puts 'rankdir=LR;' with_each_class(input_dir) do |cls| if cls["extends"] && cls["extends"] != "Object" puts arrow(canonical(cls["extends"]), cls['name'], '[style=bold,weight=10]') end if cls["mixins"] cls["mixins"].each {|mx| puts arrow(canonical(mx), cls['name'], '[weight=1,style=dashed]') } end end puts '}'