Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/yard/code_objects/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def push(value)
# Regular expression to match a fully qualified method def (self.foo, Class.foo).
METHODMATCH = /(?:(?:#{NAMESPACEMATCH}|[a-z]\w*)\s*(?:#{CSEPQ}|#{NSEPQ})\s*)?#{METHODNAMEMATCH}/

# Regular expression to match symbol and string literals
LITERALMATCH = /:\w+|'[^']*'|"[^"]*"/
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this constant make sense here?


# All builtin Ruby exception classes for inheritance tree.
BUILTIN_EXCEPTIONS = ["ArgumentError", "ClosedQueueError", "EncodingError",
"EOFError", "Exception", "FiberError", "FloatDomainError", "IndexError",
Expand Down
38 changes: 31 additions & 7 deletions lib/yard/tags/types_explainer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,14 @@ def initialize(name)
end

def to_s(singular = true)
if name[0, 1] == "#"
singular ? "an object that responds to #{name}" : "objects that respond to #{name}"
elsif name[0, 1] =~ /[A-Z]/
if name[0, 1] =~ /[A-Z]/
singular ? "a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} " + name : "#{name}#{name[-1, 1] =~ /[A-Z]/ ? "'" : ''}s"
else
name
end
end

private
protected

def list_join(list)
index = 0
Expand All @@ -54,6 +52,20 @@ def list_join(list)
end
end

# @private
class LiteralType < Type
def to_s(_singular = true)
"a literal value #{name}"
end
end

# @private
class DuckType < Type
def to_s(singular = true)
singular ? "an object that responds to #{name}" : "objects that respond to #{name}"
end
end

# @private
class CollectionType < Type
attr_accessor :types
Expand Down Expand Up @@ -101,7 +113,7 @@ class Parser
:collection_end => />/,
:fixed_collection_start => /\(/,
:fixed_collection_end => /\)/,
:type_name => /#{ISEP}#{METHODNAMEMATCH}|#{NAMESPACEMATCH}|\w+/,
:type_name => /#{ISEP}#{METHODNAMEMATCH}|#{NAMESPACEMATCH}|#{LITERALMATCH}|\w+/,
:type_next => /[,;]/,
:whitespace => /\s+/,
:hash_collection_start => /\{/,
Expand Down Expand Up @@ -135,7 +147,7 @@ def parse
name = token
when :type_next
raise SyntaxError, "expecting name, got '#{token}' at #{@scanner.pos}" if name.nil?
type = Type.new(name) unless type
type = create_type(name) unless type
types << type
type = nil
name = nil
Expand All @@ -148,14 +160,26 @@ def parse
type = HashCollectionType.new(name, parse, parse)
when :hash_collection_next, :hash_collection_end, :fixed_collection_end, :collection_end, :parse_end
raise SyntaxError, "expecting name, got '#{token}'" if name.nil?
type = Type.new(name) unless type
type = create_type(name) unless type
types << type
return types
end
end
raise SyntaxError, "invalid character at #{@scanner.peek(1)}" unless found
end
end

private

def create_type(name)
if name[0, 1] == ":" || (name[0, 1] =~ /['"]/ && name[-1, 1] =~ /['"]/)
LiteralType.new(name)
elsif name[0, 1] == "#"
DuckType.new(name)
else
Type.new(name)
end
end
end
end
end
Expand Down
20 changes: 20 additions & 0 deletions spec/code_objects/constants_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ def silence_warnings
end
end

describe :LITERALMATCH do
it "matches symbol literals" do
expect(":symbol"[CodeObjects::LITERALMATCH]).to eq ":symbol"
expect(":some_symbol"[CodeObjects::LITERALMATCH]).to eq ":some_symbol"
expect("not_a_symbol"[CodeObjects::LITERALMATCH]).to be nil
end

it "matches single-quoted string literals" do
expect("'string'"[CodeObjects::LITERALMATCH]).to eq "'string'"
expect("'some string with spaces'"[CodeObjects::LITERALMATCH]).to eq "'some string with spaces'"
expect("not_quoted"[CodeObjects::LITERALMATCH]).to be nil
end

it "matches double-quoted string literals" do
expect('"string"'[CodeObjects::LITERALMATCH]).to eq '"string"'
expect('"some string with spaces"'[CodeObjects::LITERALMATCH]).to eq '"some string with spaces"'
expect("not_quoted"[CodeObjects::LITERALMATCH]).to be nil
end
end

describe :BUILTIN_EXCEPTIONS do
it "includes all base exceptions" do
bad_names = []
Expand Down
42 changes: 34 additions & 8 deletions spec/tags/types_explainer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

RSpec.describe YARD::Tags::TypesExplainer do
Type = YARD::Tags::TypesExplainer::Type
LiteralType = YARD::Tags::TypesExplainer::LiteralType
DuckType = YARD::Tags::TypesExplainer::DuckType
CollectionType = YARD::Tags::TypesExplainer::CollectionType
FixedCollectionType = YARD::Tags::TypesExplainer::FixedCollectionType
HashCollectionType = YARD::Tags::TypesExplainer::HashCollectionType
Expand Down Expand Up @@ -32,12 +34,6 @@ def parse_fail(types)
expect(@t.to_s(false)).to eq "Arrays"
end

it "works for a method (ducktype)" do
@t.name = "#mymethod"
expect(@t.to_s).to eq "an object that responds to #mymethod"
expect(@t.to_s(false)).to eq "objects that respond to #mymethod"
end

it "works for a constant value" do
['false', 'true', 'nil', '4'].each do |name|
@t.name = name
Expand All @@ -47,6 +43,24 @@ def parse_fail(types)
end
end

describe DuckType, '#to_s' do
it "works for a method (ducktype)" do
duck_type = DuckType.new("#mymethod")
expect(duck_type.to_s).to eq "an object that responds to #mymethod"
expect(duck_type.to_s(false)).to eq "objects that respond to #mymethod"
end
end

describe LiteralType, '#to_s' do
it "works for literal values" do
[':symbol', "'5'"].each do |name|
literal_type = LiteralType.new(name)
expect(literal_type.to_s).to eq "a literal value #{name}"
expect(literal_type.to_s(false)).to eq "a literal value #{name}"
end
end
end

describe CollectionType, '#to_s' do
before { @t = CollectionType.new("Array", nil) }

Expand Down Expand Up @@ -85,7 +99,7 @@ def parse_fail(types)
end
end

describe FixedCollectionType, '#to_s' do
describe HashCollectionType, '#to_s' do
before { @t = HashCollectionType.new("Hash", nil, nil) }

it "can contain a single key type and value type" do
Expand Down Expand Up @@ -131,6 +145,17 @@ def parse_fail(types)
expect(type[3].name).to eq "E"
end

it 'parses a list of literal values' do
type = parse("true, false, nil, 4, :symbol, '5'")
expect(type.size).to eq 6
expect(type[0].name).to eq "true"
expect(type[1].name).to eq "false"
expect(type[2].name).to eq "nil"
expect(type[3].name).to eq "4"
expect(type[4].name).to eq ":symbol"
expect(type[5].name).to eq "'5'"
end

it "parses a collection type" do
type = parse("MyList<String>")
expect(type.first).to be_a(CollectionType)
Expand Down Expand Up @@ -192,7 +217,8 @@ def parse_fail(types)
a Hash with keys made of (Foos or Bars) and values of (Symbols or Numbers)",
"#weird_method?, #<=>, #!=" => "an object that responds to #weird_method?;
an object that responds to #<=>;
an object that responds to #!="
an object that responds to #!=",
":symbol, 'string'" => "a literal value :symbol; a literal value 'string'"
}
expect.each do |input, expected|
explain = YARD::Tags::TypesExplainer.explain(input)
Expand Down
Loading