From 53d7469aea3b96648ba32d1ad888074ff2cf1038 Mon Sep 17 00:00:00 2001 From: Rene Saarsoo Date: Mon, 6 Aug 2012 13:46:30 +0300 Subject: [PATCH] Implement support for @override tag. First off just a tag-based implementation of Ext4 overrides. --- lib/jsduck/aggregator.rb | 6 ++ lib/jsduck/app.rb | 1 + lib/jsduck/doc_ast.rb | 1 + lib/jsduck/doc_parser.rb | 10 +++ lib/jsduck/override.rb | 82 +++++++++++++++++++++ spec/aggregator_overrides_spec.rb | 116 ++++++++++++++++++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 lib/jsduck/override.rb diff --git a/lib/jsduck/aggregator.rb b/lib/jsduck/aggregator.rb index 0e98f22d..b98b37e9 100644 --- a/lib/jsduck/aggregator.rb +++ b/lib/jsduck/aggregator.rb @@ -2,6 +2,7 @@ require 'jsduck/class' require 'jsduck/accessors' require 'jsduck/logger' require 'jsduck/enum' +require 'jsduck/override' module JsDuck @@ -231,6 +232,11 @@ module JsDuck Enum.new(@classes).process_all! end + # Processes all overrides + def process_overrides + Override.new(@classes, @documentation).process_all! + end + # Are we dealing with ExtJS 4? # True if any of the classes is defined with Ext.define() def ext4? diff --git a/lib/jsduck/app.rb b/lib/jsduck/app.rb index f9b14129..c19fcf61 100644 --- a/lib/jsduck/app.rb +++ b/lib/jsduck/app.rb @@ -130,6 +130,7 @@ module JsDuck agr.append_ext4_event_options end agr.process_enums + agr.process_overrides agr.result end diff --git a/lib/jsduck/doc_ast.rb b/lib/jsduck/doc_ast.rb index 9a1200f5..e147ecbc 100644 --- a/lib/jsduck/doc_ast.rb +++ b/lib/jsduck/doc_ast.rb @@ -52,6 +52,7 @@ module JsDuck :requires => detect_list(:requires, doc_map), :uses => detect_list(:uses, doc_map), :enum => detect_enum(doc_map), + :override => extract(doc_map, :override, :class), }, doc_map) end diff --git a/lib/jsduck/doc_parser.rb b/lib/jsduck/doc_parser.rb index 3d7c8590..7fec36d5 100644 --- a/lib/jsduck/doc_parser.rb +++ b/lib/jsduck/doc_parser.rb @@ -132,6 +132,8 @@ module JsDuck at_throws elsif look(/@enum\b/) at_enum + elsif look(/@override\b/) + at_override elsif look(/@inheritable\b/) boolean_at_tag(/@inheritable/, :inheritable) elsif look(/@accessor\b/) @@ -294,6 +296,14 @@ module JsDuck skip_white end + # matches @override name ... + def at_override + match(/@override/) + add_tag(:override) + maybe_ident_chain(:class) + skip_white + end + # matches @type {type} or @type type # # The presence of @type implies that we are dealing with property. diff --git a/lib/jsduck/override.rb b/lib/jsduck/override.rb new file mode 100644 index 00000000..88668255 --- /dev/null +++ b/lib/jsduck/override.rb @@ -0,0 +1,82 @@ +module JsDuck + + class Override + def initialize(classes_hash, classes_array) + @classes_hash = classes_hash + @classes_array = classes_array + end + + # Applies all override classes to target classes + def process_all! + overrides = [] + + @classes_array.each do |cls| + if cls[:override] + process(cls) + overrides << cls + end + end + + # Discard override classes + overrides.each do |cls| + @classes_hash.delete(cls[:name]) + @classes_array.delete(cls) + end + end + + private + + # Applies override class to target class + def process(override) + target = @classes_hash[override[:override]] + unless target + ctx = override[:files][0] + return Logger.instance.warn(:extend, "Class #{override[:override]} not found", ctx[:filename], ctx[:linenr]) + end + + # Combine comments of classes + if override[:doc].length > 0 + target[:doc] += "\n\n**From override #{override[:name]}:** " + override[:doc] + end + target[:files] += override[:files] + + # Build lookup table of existing members + existing = {} + each_member(target) do |m| + existing[m[:id]] = m + end + + # When the same member exists in overridden class, just append + # the docs. Otherwise add the member as a whole to the class. + each_member(override) do |m| + ex = existing[m[:id]] + if ex + if m[:doc].length > 0 + ex[:doc] += "\n\n**From override #{override[:name]}:** " + m[:doc] + else + ex[:doc] += "\n\n**Overridden in #{override[:name]}.**" + end + ex[:files] += m[:files] + else + add_member(target, m) + m[:doc] += "\n\n**Defined in override #{override[:name]}.**" + end + end + end + + # helpers + + def each_member(cls) + [:members, :statics].each do |category| + cls[category].each_pair do |key, members| + members.each {|m| yield m } + end + end + end + + def add_member(cls, m) + cls[m[:static] ? :statics : :members][m[:tagname]] << m + end + end + +end diff --git a/spec/aggregator_overrides_spec.rb b/spec/aggregator_overrides_spec.rb index 304907f3..fb0a0849 100644 --- a/spec/aggregator_overrides_spec.rb +++ b/spec/aggregator_overrides_spec.rb @@ -7,6 +7,7 @@ describe JsDuck::Aggregator do def parse(string) agr = JsDuck::Aggregator.new agr.aggregate(JsDuck::SourceFile.new(string)) + agr.process_overrides JsDuck::Relations.new(agr.result.map {|cls| JsDuck::Class.new(cls) }) end @@ -77,5 +78,120 @@ describe JsDuck::Aggregator do end end + def create_members_map(cls) + r = {} + cls.all_local_members.each do |m| + r[m[:name]] = m + end + r + end + + describe "defining @override for a class" do + let(:classes) do + parse(<<-EOF) + /** + * @class Foo + * Foo comment. + */ + /** + * @method foo + * Foo comment. + */ + /** + * @method foobar + * Original comment. + */ + + /** + * @class FooOverride + * @override Foo + * FooOverride comment. + */ + /** + * @method bar + * Bar comment. + */ + /** + * @method foobar + * Override comment. + */ + EOF + end + + let(:methods) { create_members_map(classes["Foo"]) } + + it "keeps the original class" do + classes["Foo"].should_not == nil + end + + it "throws away the override" do + classes["FooOverride"].should == nil + end + + it "combines class doc with doc from override" do + classes["Foo"][:doc].should == "Foo comment.\n\n**From override FooOverride:** FooOverride comment." + end + + it "adds override to list of source files" do + classes["Foo"][:files].length.should == 2 + end + + it "keeps its original foo method" do + methods["foo"].should_not == nil + end + + it "gets the new bar method from override" do + methods["bar"].should_not == nil + end + + it "adds special override comment to bar method" do + methods["bar"][:doc].should == "Bar comment.\n\n**Defined in override FooOverride.**" + end + + it "keeps the foobar method that's in both original and override" do + methods["foobar"].should_not == nil + end + + it "combines docs of original and override" do + methods["foobar"][:doc].should == "Original comment.\n\n**From override FooOverride:** Override comment." + end + + it "adds override source to list of files to overridden member" do + methods["foobar"][:files].length.should == 2 + end + end + + describe "comment-less @override for a class" do + let(:classes) do + parse(<<-EOF) + /** + * @class Foo + * Foo comment. + */ + /** + * @method foobar + * Original comment. + */ + + /** + * @class FooOverride + * @override Foo + */ + /** + * @method foobar + */ + EOF + end + + let(:methods) { create_members_map(classes["Foo"]) } + + it "adds no doc from override to the class itself" do + classes["Foo"][:doc].should == "Foo comment." + end + + it "adds note about override to member" do + methods["foobar"][:doc].should == "Original comment.\n\n**Overridden in FooOverride.**" + end + end end -- GitLab