Loading lib/jsduck/class.rb +11 −112 Original line number Diff line number Diff line require 'jsduck/logger' require 'jsduck/members_index' module JsDuck Loading @@ -8,6 +9,10 @@ module JsDuck class Class attr_accessor :relations # Used only by MembersIndex class itself to access the # MembersIndex instances of parents and mixins. attr_accessor :members_index # Creates JSDuck class. # # Pass true as second parameter to create a placeholder class. Loading @@ -20,6 +25,8 @@ module JsDuck @doc[:members] = [] if !@doc[:members] @members_index = MembersIndex.new(self) @relations = nil end Loading Loading @@ -126,12 +133,12 @@ module JsDuck # When nothing found, an empty array is returned. def find_members(query={}) if query[:name] ms = global_members_hash_by_name[query[:name]] || [] ms = @members_index.global_by_name[query[:name]] || [] ms = ms.find_all {|m| m[:owner] == @doc[:name]} if query[:local] elsif query[:local] ms = local_members_hash.values ms = @members_index.local_by_id.values else ms = global_members_hash.values ms = @members_index.global_by_id.values end if query[:tagname] Loading @@ -147,121 +154,13 @@ module JsDuck ms end # Generates global members hash by name def global_members_hash_by_name unless @global_map_by_name @global_map_by_name = {} global_members_hash.each_pair do |id, m| @global_map_by_name[m[:name]] = [] unless @global_map_by_name[m[:name]] @global_map_by_name[m[:name]] << m end end @global_map_by_name end # Generates global members hash by ID def global_members_hash unless @global_map_by_id # Make copy of parent class members. # Otherwise we'll be merging directly into parent class. @global_map_by_id = parent ? parent.global_members_hash.clone : {} mixins.each do |mix| merge!(@global_map_by_id, mix.global_members_hash) end # Exclude all non-inheritable static members @global_map_by_id.delete_if {|id, m| m[:meta][:static] && !m[:inheritable] } merge!(@global_map_by_id, local_members_hash) end @global_map_by_id end # Generates local members hash by ID def local_members_hash unless @map_by_id @map_by_id = {} @doc[:members].each do |m| @map_by_id[m[:id]] = m end end @map_by_id end # merges second members hash into first one def merge!(hash1, hash2, skip_overrides=false) hash2.each_pair do |name, m| if m[:meta] && m[:meta][:hide] if hash1[name] hash1.delete(name) else ctx = m[:files][0] msg = "@hide used but #{m[:tagname]} #{m[:name]} not found in parent class" Logger.instance.warn(:hide, msg, ctx[:filename], ctx[:linenr]) end else if hash1[name] store_overrides(hash1[name], m) end hash1[name] = m end end end # Invoked when merge! finds two members with the same name. # New member always overrides the old, but inside new we keep # a list of members it overrides. Normally one member will # override one other member, but a member from mixin can override # multiple members - although there's not a single such case in # ExtJS, we have to handle it. # # Every overridden member is listed just once. def store_overrides(old, new) # Sometimes a class is included multiple times (like Ext.Base) # resulting in its members overriding themselves. Because of # this, ignore overriding itself. if new[:owner] != old[:owner] new[:overrides] = [] unless new[:overrides] unless new[:overrides].any? {|m| m[:owner] == old[:owner] } # Make a copy of the important properties for us. We can't # just push the actual `old` member itself, because there # can be circular overrides (notably with Ext.Base), which # will result in infinite loop when we try to convert our # class into JSON. new[:overrides] << { :name => old[:name], :owner => old[:owner], :id => old[:id], } end end end # This must be called whenever member hashes are changed. # It updates the :id fields of members and clears the caches. def update_members!(members) members.each do |m| m[:id] = Class.member_id(m) end invalidate_search_cache! end # Clears the search cache. # This is also REALLY BAD - try to get rid of it. def invalidate_search_cache! @map_by_id = nil @global_map_by_id = nil @global_map_by_name = nil parent.invalidate_search_cache! if parent mixins.each {|mix| mix.invalidate_search_cache! } @members_index.invalidate! end # Returns all local members of class Loading lib/jsduck/members_index.rb 0 → 100644 +132 −0 Original line number Diff line number Diff line require 'jsduck/logger' module JsDuck # Helper for JsDuck::Class for indexing its members. # # While indexing the members of a class it accesses the MembersIndex # instances of parent and mixins of that class through the # #members_index accessor. This isn't the nicest approach, but # better than having all of this functionality inside the # JsDuck::Class itself. class MembersIndex def initialize(cls) @cls = cls @map_by_id = nil @global_map_by_id = nil @global_map_by_name = nil end # Returns hash of all members by name (including inherited ones) def global_by_name unless @global_map_by_name @global_map_by_name = {} global_by_id.each_pair do |id, m| @global_map_by_name[m[:name]] = [] unless @global_map_by_name[m[:name]] @global_map_by_name[m[:name]] << m end end @global_map_by_name end # Returns hash of all members by ID (including inherited ones) def global_by_id unless @global_map_by_id # Make copy of parent class members. # Otherwise we'll be merging directly into parent class. @global_map_by_id = @cls.parent ? @cls.parent.members_index.global_by_id.clone : {} @cls.mixins.each do |mix| merge!(@global_map_by_id, mix.members_index.global_by_id) end # Exclude all non-inheritable static members @global_map_by_id.delete_if {|id, m| m[:meta][:static] && !m[:inheritable] } merge!(@global_map_by_id, local_by_id) end @global_map_by_id end # Returns hash of local members by ID (no inherited members) def local_by_id unless @map_by_id @map_by_id = {} @cls[:members].each do |m| @map_by_id[m[:id]] = m end end @map_by_id end # Clears the search cache. # Using this is REALLY BAD - try to get rid of it. def invalidate! @map_by_id = nil @global_map_by_id = nil @global_map_by_name = nil @cls.parent.members_index.invalidate! if @cls.parent @cls.mixins.each {|mix| mix.members_index.invalidate! } end private # merges second members hash into first one def merge!(hash1, hash2) hash2.each_pair do |name, m| if m[:meta] && m[:meta][:hide] if hash1[name] hash1.delete(name) else ctx = m[:files][0] msg = "@hide used but #{m[:tagname]} #{m[:name]} not found in parent class" Logger.instance.warn(:hide, msg, ctx[:filename], ctx[:linenr]) end else if hash1[name] store_overrides(hash1[name], m) end hash1[name] = m end end end # Invoked when merge! finds two members with the same name. # New member always overrides the old, but inside new we keep # a list of members it overrides. Normally one member will # override one other member, but a member from mixin can override # multiple members - although there's not a single such case in # ExtJS, we have to handle it. # # Every overridden member is listed just once. def store_overrides(old, new) # Sometimes a class is included multiple times (like Ext.Base) # resulting in its members overriding themselves. Because of # this, ignore overriding itself. if new[:owner] != old[:owner] new[:overrides] = [] unless new[:overrides] unless new[:overrides].any? {|m| m[:owner] == old[:owner] } # Make a copy of the important properties for us. We can't # just push the actual `old` member itself, because there # can be circular overrides (notably with Ext.Base), which # will result in infinite loop when we try to convert our # class into JSON. new[:overrides] << { :name => old[:name], :owner => old[:owner], :id => old[:id], } end end end end end spec/class_spec.rb +4 −6 Original line number Diff line number Diff line Loading @@ -226,16 +226,15 @@ describe JsDuck::Class do describe "then after changing child member name" do before do child[:members][0][:name] = "changedName" child[:members][0][:id] = "property-changedName" end it "the new member can't be found" do child.find_members(:name => "changedName").length.should == 0 end describe "and after calling #invalidate_search_cache!" do describe "and after calling #update_members!" do before do child.invalidate_search_cache! child.update_members!(child[:members]) end it "the new member is now findable" do Loading @@ -247,16 +246,15 @@ describe JsDuck::Class do describe "then after changing parent member tagname" do before do parent[:members][0][:tagname] = :method parent[:members][0][:id] = "method-oldName" end it "the new member can't be found" do child.find_members(:tagname => :method).length.should == 0 end describe "and after calling #invalidate_search_cache!" do describe "and after calling #update_members!" do before do child.invalidate_search_cache! child.update_members!(parent[:members]) end it "the new member is now findable" do Loading Loading
lib/jsduck/class.rb +11 −112 Original line number Diff line number Diff line require 'jsduck/logger' require 'jsduck/members_index' module JsDuck Loading @@ -8,6 +9,10 @@ module JsDuck class Class attr_accessor :relations # Used only by MembersIndex class itself to access the # MembersIndex instances of parents and mixins. attr_accessor :members_index # Creates JSDuck class. # # Pass true as second parameter to create a placeholder class. Loading @@ -20,6 +25,8 @@ module JsDuck @doc[:members] = [] if !@doc[:members] @members_index = MembersIndex.new(self) @relations = nil end Loading Loading @@ -126,12 +133,12 @@ module JsDuck # When nothing found, an empty array is returned. def find_members(query={}) if query[:name] ms = global_members_hash_by_name[query[:name]] || [] ms = @members_index.global_by_name[query[:name]] || [] ms = ms.find_all {|m| m[:owner] == @doc[:name]} if query[:local] elsif query[:local] ms = local_members_hash.values ms = @members_index.local_by_id.values else ms = global_members_hash.values ms = @members_index.global_by_id.values end if query[:tagname] Loading @@ -147,121 +154,13 @@ module JsDuck ms end # Generates global members hash by name def global_members_hash_by_name unless @global_map_by_name @global_map_by_name = {} global_members_hash.each_pair do |id, m| @global_map_by_name[m[:name]] = [] unless @global_map_by_name[m[:name]] @global_map_by_name[m[:name]] << m end end @global_map_by_name end # Generates global members hash by ID def global_members_hash unless @global_map_by_id # Make copy of parent class members. # Otherwise we'll be merging directly into parent class. @global_map_by_id = parent ? parent.global_members_hash.clone : {} mixins.each do |mix| merge!(@global_map_by_id, mix.global_members_hash) end # Exclude all non-inheritable static members @global_map_by_id.delete_if {|id, m| m[:meta][:static] && !m[:inheritable] } merge!(@global_map_by_id, local_members_hash) end @global_map_by_id end # Generates local members hash by ID def local_members_hash unless @map_by_id @map_by_id = {} @doc[:members].each do |m| @map_by_id[m[:id]] = m end end @map_by_id end # merges second members hash into first one def merge!(hash1, hash2, skip_overrides=false) hash2.each_pair do |name, m| if m[:meta] && m[:meta][:hide] if hash1[name] hash1.delete(name) else ctx = m[:files][0] msg = "@hide used but #{m[:tagname]} #{m[:name]} not found in parent class" Logger.instance.warn(:hide, msg, ctx[:filename], ctx[:linenr]) end else if hash1[name] store_overrides(hash1[name], m) end hash1[name] = m end end end # Invoked when merge! finds two members with the same name. # New member always overrides the old, but inside new we keep # a list of members it overrides. Normally one member will # override one other member, but a member from mixin can override # multiple members - although there's not a single such case in # ExtJS, we have to handle it. # # Every overridden member is listed just once. def store_overrides(old, new) # Sometimes a class is included multiple times (like Ext.Base) # resulting in its members overriding themselves. Because of # this, ignore overriding itself. if new[:owner] != old[:owner] new[:overrides] = [] unless new[:overrides] unless new[:overrides].any? {|m| m[:owner] == old[:owner] } # Make a copy of the important properties for us. We can't # just push the actual `old` member itself, because there # can be circular overrides (notably with Ext.Base), which # will result in infinite loop when we try to convert our # class into JSON. new[:overrides] << { :name => old[:name], :owner => old[:owner], :id => old[:id], } end end end # This must be called whenever member hashes are changed. # It updates the :id fields of members and clears the caches. def update_members!(members) members.each do |m| m[:id] = Class.member_id(m) end invalidate_search_cache! end # Clears the search cache. # This is also REALLY BAD - try to get rid of it. def invalidate_search_cache! @map_by_id = nil @global_map_by_id = nil @global_map_by_name = nil parent.invalidate_search_cache! if parent mixins.each {|mix| mix.invalidate_search_cache! } @members_index.invalidate! end # Returns all local members of class Loading
lib/jsduck/members_index.rb 0 → 100644 +132 −0 Original line number Diff line number Diff line require 'jsduck/logger' module JsDuck # Helper for JsDuck::Class for indexing its members. # # While indexing the members of a class it accesses the MembersIndex # instances of parent and mixins of that class through the # #members_index accessor. This isn't the nicest approach, but # better than having all of this functionality inside the # JsDuck::Class itself. class MembersIndex def initialize(cls) @cls = cls @map_by_id = nil @global_map_by_id = nil @global_map_by_name = nil end # Returns hash of all members by name (including inherited ones) def global_by_name unless @global_map_by_name @global_map_by_name = {} global_by_id.each_pair do |id, m| @global_map_by_name[m[:name]] = [] unless @global_map_by_name[m[:name]] @global_map_by_name[m[:name]] << m end end @global_map_by_name end # Returns hash of all members by ID (including inherited ones) def global_by_id unless @global_map_by_id # Make copy of parent class members. # Otherwise we'll be merging directly into parent class. @global_map_by_id = @cls.parent ? @cls.parent.members_index.global_by_id.clone : {} @cls.mixins.each do |mix| merge!(@global_map_by_id, mix.members_index.global_by_id) end # Exclude all non-inheritable static members @global_map_by_id.delete_if {|id, m| m[:meta][:static] && !m[:inheritable] } merge!(@global_map_by_id, local_by_id) end @global_map_by_id end # Returns hash of local members by ID (no inherited members) def local_by_id unless @map_by_id @map_by_id = {} @cls[:members].each do |m| @map_by_id[m[:id]] = m end end @map_by_id end # Clears the search cache. # Using this is REALLY BAD - try to get rid of it. def invalidate! @map_by_id = nil @global_map_by_id = nil @global_map_by_name = nil @cls.parent.members_index.invalidate! if @cls.parent @cls.mixins.each {|mix| mix.members_index.invalidate! } end private # merges second members hash into first one def merge!(hash1, hash2) hash2.each_pair do |name, m| if m[:meta] && m[:meta][:hide] if hash1[name] hash1.delete(name) else ctx = m[:files][0] msg = "@hide used but #{m[:tagname]} #{m[:name]} not found in parent class" Logger.instance.warn(:hide, msg, ctx[:filename], ctx[:linenr]) end else if hash1[name] store_overrides(hash1[name], m) end hash1[name] = m end end end # Invoked when merge! finds two members with the same name. # New member always overrides the old, but inside new we keep # a list of members it overrides. Normally one member will # override one other member, but a member from mixin can override # multiple members - although there's not a single such case in # ExtJS, we have to handle it. # # Every overridden member is listed just once. def store_overrides(old, new) # Sometimes a class is included multiple times (like Ext.Base) # resulting in its members overriding themselves. Because of # this, ignore overriding itself. if new[:owner] != old[:owner] new[:overrides] = [] unless new[:overrides] unless new[:overrides].any? {|m| m[:owner] == old[:owner] } # Make a copy of the important properties for us. We can't # just push the actual `old` member itself, because there # can be circular overrides (notably with Ext.Base), which # will result in infinite loop when we try to convert our # class into JSON. new[:overrides] << { :name => old[:name], :owner => old[:owner], :id => old[:id], } end end end end end
spec/class_spec.rb +4 −6 Original line number Diff line number Diff line Loading @@ -226,16 +226,15 @@ describe JsDuck::Class do describe "then after changing child member name" do before do child[:members][0][:name] = "changedName" child[:members][0][:id] = "property-changedName" end it "the new member can't be found" do child.find_members(:name => "changedName").length.should == 0 end describe "and after calling #invalidate_search_cache!" do describe "and after calling #update_members!" do before do child.invalidate_search_cache! child.update_members!(child[:members]) end it "the new member is now findable" do Loading @@ -247,16 +246,15 @@ describe JsDuck::Class do describe "then after changing parent member tagname" do before do parent[:members][0][:tagname] = :method parent[:members][0][:id] = "method-oldName" end it "the new member can't be found" do child.find_members(:tagname => :method).length.should == 0 end describe "and after calling #invalidate_search_cache!" do describe "and after calling #update_members!" do before do child.invalidate_search_cache! child.update_members!(parent[:members]) end it "the new member is now findable" do Loading