Skip to content

Commit

Permalink
Add rbs types and corresponding lint check (#57)
Browse files Browse the repository at this point in the history
First:

- Add `rbs` gem and all `rbs` types in `sig/` directory
- Add `steep` gem for easier rbs type checking, with basic configuration
only checking `lib` for now
- Add `rubyzip` types as they are not defined

Then:

- Add `|| raise` for places where we expect to always have a value, but
that rbs/steep are not smart enough to autodetect
- Add `EMPTY_ENUMERATOR` to be returned when there is no iteration to
perform
- Add `_Nilable` interface to allow for `nil?` check on generic type for
`ProviderCollection#contains?`
- Expand `ProviderCollection#resolve` to `key?` check to prevent
rbs/steep from complaining about `Cannot have body method`
- Add `children_match_prefix` `partial_word_chars?` `word_chars?`
`closest_node` private abstract methods to `Nodes::Node`, so that both
`Nodes::Raw` and `Nodes::Compressed` have it without duplication
- Make `yardoc` match `rbs` types

Finally:

- Rename `char_symbols` to `reversed_char_symbols` for clarity
- Rename `Nodes::Raw#add`'s param `chars` to `reversed_chars` for
clarity
- Add `lint-rbs-steep` action
- Rename `lint` => `lint-rubocop`
- Bump lint and coverage ruby versions to `3.3.6`
  • Loading branch information
gonzedge authored Nov 30, 2024
1 parent 6b55683 commit 692d20b
Show file tree
Hide file tree
Showing 40 changed files with 691 additions and 49 deletions.
7 changes: 6 additions & 1 deletion .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
version: "2"
exclude_patterns:
- 'tasks/'
checks:
method-complexity:
config:
threshold: 7
plugins:
# No to-dos or similar
fixme:
Expand All @@ -10,7 +15,7 @@ plugins:
flog:
enabled: true
config:
score_threshold: 25.0
score_threshold: 28.5
exclude_patterns:
- 'spec/'
# Markdown lint with rules from https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md
Expand Down
17 changes: 14 additions & 3 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,28 @@ permissions:
contents: read

jobs:
lint:
lint-rubocop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.0
ruby-version: 3.3.6
bundler-cache: true
- name: Run rubocop
run: bundle exec rubocop
lint-rbs-steep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.6
bundler-cache: true
- name: Run rbs + steep
run: bundle exec steep check
spec:
runs-on: ubuntu-latest
strategy:
Expand Down Expand Up @@ -63,7 +74,7 @@ jobs:
- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.0
ruby-version: 3.3.6
bundler-cache: true
- name: Report rspec test coverage to coveralls.io
env:
Expand Down
5 changes: 5 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,8 @@ Metrics/AbcSize:
# The ABC size is a calculated magnitude, so this number can be an Integer or
# a Float.
Max: 20
AllowedMethods:
- 'closest_node'
Exclude:
- 'tasks/*.rb'

Expand All @@ -1353,6 +1355,9 @@ Metrics/ClassLength:
# Avoid complex methods.
Metrics/CyclomaticComplexity:
Max: 6
AllowedMethods:
- 'children_match_prefix'
- 'closest_node'

Metrics/MethodLength:
CountComments: false # count full line comments?
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ group :development do
gem 'flamegraph'
gem 'memory_profiler'
gem 'pry'
gem 'racc'
gem 'rake'
gem 'rbs'
gem 'rspec'
gem 'ruby-prof'
gem 'stackprof'
gem 'steep'
gem 'yard'
end

Expand Down
35 changes: 35 additions & 0 deletions Steepfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

# D = Steep::Diagnostic
#
target :lib do
signature 'sig'

check 'lib'
# check 'tasks'

# check 'Gemfile'
# check 'Guardfile'
# check 'Rakefile'
# check 'Steepfile'

# library 'rubyzip'
library 'yaml'
library 'securerandom'

# configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
# configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
# configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
# configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
# configure_code_diagnostics do |hash| # You can setup everything yourself
# hash[D::Ruby::NoMethod] = :information
# end
end

# target :test do
# signature 'sig', 'sig-private'
#
# check 'test'
#
# # library 'pathname' # Standard libraries
# end
6 changes: 3 additions & 3 deletions lib/rambling/trie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def create filepath = nil, reader = nil
if filepath
reader ||= readers.resolve filepath
# noinspection RubyMismatchedArgumentType,RubyNilAnalysis
reader.each_word filepath do |word|
(reader || raise).each_word filepath do |word|
container << word
end
end
Expand All @@ -48,7 +48,7 @@ def create filepath = nil, reader = nil
# discouraged. Only use the +.marshal+ format with trusted input.
def load filepath, serializer = nil
serializer ||= serializers.resolve filepath
root = serializer.load filepath
root = (serializer || raise).load filepath
Rambling::Trie::Container.new root, compressor do |container|
yield container if block_given?
end
Expand All @@ -66,7 +66,7 @@ def load filepath, serializer = nil
def dump trie, filepath, serializer = nil
serializer ||= serializers.resolve filepath
# noinspection RubyNilAnalysis
serializer.dump trie.root, filepath
(serializer || raise).dump trie.root, filepath
end

# Provides configuration properties for the +Rambling::Trie+ gem.
Expand Down
4 changes: 3 additions & 1 deletion lib/rambling/trie/compressor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ module Trie
# Responsible for the compression process of a trie data structure.
class Compressor
# Compresses a {Nodes::Node Node} from a trie data structure.
# @param [Nodes::Raw] node the node to compress.
# @param [Nodes::Node] node the node to compress.
# @return [Nodes::Compressed] node the compressed version of the node.
def compress node
return if node.nil?

if node.compressible?
compress_child_and_merge node
else
Expand Down
12 changes: 10 additions & 2 deletions lib/rambling/trie/configuration/provider_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ def providers
# @param [String] filepath the filepath to resolve into a provider.
# @return [TProvider, nil] the provider for the given file's extension. {#default} if not found.
def resolve filepath
providers[file_format filepath] || default
extension = file_format filepath
if providers.key? extension
providers[extension]
else
default
end
end

# Resets the provider collection to the initial values.
Expand Down Expand Up @@ -104,7 +109,10 @@ def file_format filepath
end

def contains? provider
provider.nil? || (providers.any? && provider_instances.include?(provider))
return true if provider.nil?

p = provider || raise
providers.any? && provider_instances.include?(p)
end

alias_method :provider_instances, :values
Expand Down
14 changes: 8 additions & 6 deletions lib/rambling/trie/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def initialize root, compressor
# @see Nodes::Raw#add
# @see Nodes::Compressed#add
def add word
root.add char_symbols word
root.add reversed_char_symbols word
end

# Adds all provided words to the trie.
Expand Down Expand Up @@ -98,7 +98,7 @@ def scan word = ''

# Returns all words within a string that match a word contained in the trie.
# @param [String] phrase the string to look for matching words in.
# @return [Enumerator<String>] all the words in the given string that match a word in the trie.
# @return [Array<String>] all the words in the given string that match a word in the trie.
# @yield [String] each word found in phrase.
def words_within phrase
words_within_root(phrase).to_a
Expand Down Expand Up @@ -201,19 +201,21 @@ def words_within_root phrase
return enum_for :words_within_root, phrase unless block_given?

chars = phrase.chars
# rubocop:disable Style/CommentedKeyword
0.upto(chars.length - 1).each do |starting_index|
new_phrase = chars.slice starting_index..(chars.length - 1)
new_phrase = chars.slice starting_index..(chars.length - 1) # : Array[String]
root.match_prefix new_phrase do |word|
yield word
end
end
end # : Enumerator[String, void]
# rubocop:enable Style/CommentedKeyword
end

def compress_root
compressor.compress root
compressor.compress root # : Nodes::Compressed
end

def char_symbols word
def reversed_char_symbols word
symbols = []
word.reverse.each_char { |c| symbols << c.to_sym }
symbols
Expand Down
2 changes: 2 additions & 0 deletions lib/rambling/trie/enumerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module Trie
module Enumerable
include ::Enumerable

EMPTY_ENUMERATOR = [].to_enum :each

# Returns number of words contained in the trie
# @see https://ruby-doc.org/3.3.0/Enumerable.html#method-i-count Enumerable#count
alias_method :size, :count
Expand Down
22 changes: 11 additions & 11 deletions lib/rambling/trie/nodes/compressed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ def compressed?
private

def partial_word_chars? chars
child = children_tree[chars.first.to_sym]
child = children_tree[(chars.first || raise).to_sym]
return false unless child

child_letter = child.letter.to_s

if chars.size >= child_letter.size
letter = chars.slice!(0, child_letter.size).join
letter = (chars.slice!(0, child_letter.size) || raise).join
return child.partial_word? chars if child_letter == letter
end

Expand All @@ -39,7 +39,7 @@ def partial_word_chars? chars
end

def word_chars? chars
letter = chars.slice! 0
letter = chars.slice!(0) || raise
letter_sym = letter.to_sym

child = children_tree[letter_sym]
Expand All @@ -50,21 +50,21 @@ def word_chars? chars

break if chars.empty?

letter << chars.slice!(0)
letter << (chars.slice!(0) || raise)
letter_sym = letter.to_sym
end

false
end

def closest_node chars
child = children_tree[chars.first.to_sym]
child = children_tree[(chars.first || raise).to_sym]
return missing unless child

child_letter = child.letter.to_s

if chars.size >= child_letter.size
letter = chars.slice!(0, child_letter.size).join
letter = (chars.slice!(0, child_letter.size) || raise).join
return child.scan chars if child_letter == letter
end

Expand All @@ -77,15 +77,15 @@ def closest_node chars
def children_match_prefix chars
return enum_for :children_match_prefix, chars unless block_given?

return if chars.empty?
return EMPTY_ENUMERATOR if chars.empty?

child = children_tree[chars.first.to_sym]
return unless child
child = children_tree[(chars.first || raise).to_sym]
return EMPTY_ENUMERATOR unless child

child_letter = child.letter.to_s
letter = chars.slice!(0, child_letter.size).join
letter = (chars.slice!(0, child_letter.size) || raise).join

return unless child_letter == letter
return EMPTY_ENUMERATOR unless child_letter == letter

child.match_prefix chars do |word|
yield word
Expand Down
25 changes: 23 additions & 2 deletions lib/rambling/trie/nodes/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Node
# Creates a new node.
# @param [Symbol, nil] letter the Node's letter value.
# @param [Node, nil] parent the parent of the current node.
# @param [Hash<Symbol, Node>] children_tree the children tree of the current node.
def initialize letter = nil, parent = nil, children_tree = {}
@letter = letter
@parent = parent
Expand All @@ -52,6 +53,8 @@ def first_child
# rubocop:disable Lint/UnreachableLoop
children_tree.each_value { |child| return child }
# rubocop:enable Lint/UnreachableLoop

nil
end

# Indicates if the current node is the root node.
Expand Down Expand Up @@ -106,7 +109,7 @@ def scan chars
end

# Returns all words that match a prefix of any length within chars.
# @param [String] chars the chars to base the prefix on.
# @param [Array[String]] chars the chars to base the prefix on.
# @return [Enumerator<String>] all the words that match a prefix by chars.
# @yield [String] each word found.
def match_prefix chars
Expand Down Expand Up @@ -147,7 +150,7 @@ def key? letter
# Delete a given letter and its corresponding {Node Node} from
# this {Node Node}'s children tree.
# @param [Symbol] letter the letter to delete from the node's children tree.
# @return [Node] the node corresponding to the deleted letter.
# @return [Node, nil] the node corresponding to the deleted letter.
# @see https://ruby-doc.org/3.3.0/Hash.html#method-i-delete Hash#delete
def delete letter
children_tree.delete letter
Expand All @@ -164,6 +167,24 @@ def missing
private

attr_accessor :terminal

# abstract methods

def children_match_prefix chars
raise NotImplementedError
end

def partial_word_chars? chars
raise NotImplementedError
end

def word_chars? chars
raise NotImplementedError
end

def closest_node chars
raise NotImplementedError
end
end
end
end
Expand Down
Loading

0 comments on commit 692d20b

Please sign in to comment.