Skip to content

Commit 9999568

Browse files
committed
Add RBS type signatures
Introduces comprehensive RBS type signatures for the entire public API and internal classes. This provides improved type checking, better IDE support, and documentation for gem users. All type signatures have been validated through runtime type checking using `rbs test` against the full RSpec test suite (291 examples, 0 failures). Changes: - Add RBS signatures for all core classes (Zxcvbn, Tester, Score, Match, etc.) - Add rake tasks for RBS validation and testing (rbs:validate, rbs:test, rbs:list, rbs:parse) - Configure rbs_collection.yaml for dependency type signatures - Update gemspec to exclude rbs_collection.yaml from gem package - Add sig/README.md with usage instructions - Fix type signatures based on runtime validation: - Use Numeric instead of Float for entropy calculations (handles both Integer and Float) - Accept nullable parameters where nil is valid (password, Match in year_entropy) - Support Symbol keys in DictionaryRanker for test compatibility - Allow untyped arrays in user_inputs for proper sanitization
1 parent 435697f commit 9999568

23 files changed

+398
-8
lines changed

.github/workflows/ci.yml

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,32 @@ jobs:
88
ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
99
runs-on: ubuntu-latest
1010
steps:
11-
- name: Checkout
12-
uses: actions/checkout@v4
13-
- name: Set up Ruby
14-
uses: ruby/setup-ruby@v1
11+
- uses: actions/checkout@v6
12+
- uses: ruby/setup-ruby@v1
1513
with:
1614
ruby-version: ${{ matrix.ruby }}
1715
bundler-cache: true
18-
- run: bundle exec rake
16+
- name: Run tests
17+
run: bundle exec rake spec
18+
19+
rubocop:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v6
23+
- uses: ruby/setup-ruby@v1
24+
with:
25+
ruby-version: '4.0'
26+
bundler-cache: true
27+
- name: Run RuboCop
28+
run: bundle exec rake rubocop
29+
30+
rbs:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v6
34+
- uses: ruby/setup-ruby@v1
35+
with:
36+
ruby-version: '4.0'
37+
bundler-cache: true
38+
- name: Validate RBS signatures
39+
run: bundle exec rake rbs:validate

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ spec/reports
1515
test/tmp
1616
test/version_tmp
1717
tmp
18-
bin/
18+
bin/
19+
20+
# RBS
21+
.gem_rbs_collection

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Added
10+
- RBS type signatures for improved type checking and IDE support ([#68])
11+
912
### Changed
1013
- Minor fixups in gem metadata ([#67]).
1114

1215
[Unreleased]: https://github.com/envato/zxcvbn-ruby/compare/v1.3.0...HEAD
1316
[#67]: https://github.com/envato/zxcvbn-ruby/pull/67
17+
[#68]: https://github.com/envato/zxcvbn-ruby/pull/68
1418

1519
## [1.3.0] - 2026-01-02
1620

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ group :development do
1010
gem 'guard-bundler', require: false
1111
gem 'guard-rspec', require: false
1212
gem 'rake'
13+
gem 'rbs'
1314
end
1415

1516
group :test do

Rakefile

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,35 @@ require 'rubocop/rake_task'
88
RSpec::Core::RakeTask.new('spec')
99
RuboCop::RakeTask.new('rubocop')
1010

11-
task default: %i[spec rubocop]
11+
begin
12+
require 'rbs/cli'
13+
14+
namespace :rbs do
15+
desc 'Validate RBS type signatures'
16+
task :validate do
17+
sh 'rbs -I sig validate'
18+
end
19+
20+
desc 'Run RBS runtime type checker with RSpec'
21+
task :test do
22+
sh "rbs -I sig test --target 'Zxcvbn::*' rspec"
23+
end
24+
25+
desc 'List RBS types'
26+
task :list do
27+
sh 'rbs -I sig list | grep Zxcvbn'
28+
end
29+
30+
desc 'Check RBS syntax'
31+
task :parse do
32+
sh 'rbs -I sig parse sig/**/*.rbs'
33+
end
34+
end
35+
rescue LoadError
36+
# RBS is not available
37+
end
38+
39+
task default: %i[spec rubocop rbs:validate]
1240

1341
task :console do
1442
require 'zxcvbn'

rbs_collection.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# RBS collection configuration
2+
# This file specifies which type signature repositories to use
3+
4+
sources:
5+
- type: git
6+
name: ruby/gem_rbs_collection
7+
remote: https://github.com/ruby/gem_rbs_collection.git
8+
revision: main
9+
repo_dir: gems
10+
11+
path: .gem_rbs_collection

sig/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# RBS Type Signatures
2+
3+
This directory contains [RBS](https://github.com/ruby/rbs) type signatures for the zxcvbn-ruby gem.
4+
5+
## What is RBS?
6+
7+
RBS is Ruby's type signature language. It provides a way to describe the structure of Ruby programs with:
8+
- Class and module definitions
9+
- Method signatures with parameter and return types
10+
- Instance variables and constants
11+
- Duck typing and union types
12+
13+
## Usage
14+
15+
### Validating Type Signatures
16+
17+
To validate that the RBS files are syntactically correct:
18+
19+
```bash
20+
bundle exec rake rbs:validate
21+
```
22+
23+
### Runtime Type Checking
24+
25+
To run runtime type checking against the actual Ruby code during tests:
26+
27+
```bash
28+
bundle exec rake rbs:test
29+
```
30+
31+
This runs the RSpec test suite with RBS type checking enabled, verifying that method calls match their type signatures at runtime. Note: This takes about 2 minutes to run.
32+
33+
### Other Useful Commands
34+
35+
List all Zxcvbn types:
36+
```bash
37+
bundle exec rake rbs:list
38+
```
39+
40+
Check syntax of RBS files:
41+
```bash
42+
bundle exec rake rbs:parse
43+
```
44+
45+
## File Structure
46+
47+
The signatures mirror the structure of the `lib/` directory:
48+
49+
- `sig/zxcvbn.rbs` - Main Zxcvbn module
50+
- `sig/zxcvbn/*.rbs` - Core classes (Tester, Score, Match, etc.)
51+
- `sig/zxcvbn/matchers/*.rbs` - Pattern matcher classes
52+
53+
## Adding New Signatures
54+
55+
When adding new classes or methods to the codebase, remember to:
56+
57+
1. Create or update the corresponding `.rbs` file in the `sig/` directory
58+
2. Run `bundle exec rake rbs_validate` to ensure the syntax is correct
59+
3. Keep type signatures in sync with the actual implementation
60+
61+
## Resources
62+
63+
- [RBS Documentation](https://github.com/ruby/rbs)
64+
- [RBS Syntax Guide](https://github.com/ruby/rbs/blob/master/docs/syntax.md)
65+
- [Ruby Signature Collection](https://github.com/ruby/gem_rbs_collection)

sig/zxcvbn.rbs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module Zxcvbn
2+
VERSION: String
3+
4+
DATA_PATH: Pathname
5+
6+
type word_list = Hash[String, Array[String]]
7+
type user_inputs = Array[String]
8+
9+
def self.test: (String? password, ?user_inputs user_inputs, ?word_list word_lists) -> Score
10+
end

sig/zxcvbn/crack_time.rbs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Zxcvbn
2+
module CrackTime
3+
SINGLE_GUESS: Float
4+
NUM_ATTACKERS: Integer
5+
SECONDS_PER_GUESS: Float
6+
7+
def entropy_to_crack_time: (Numeric entropy) -> Float
8+
9+
def crack_time_to_score: (Numeric seconds) -> Integer
10+
11+
def display_time: (Numeric seconds) -> String
12+
end
13+
end

sig/zxcvbn/data.rbs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module Zxcvbn
2+
class Data
3+
type ranked_dictionary = Hash[String, Integer]
4+
type adjacency_graph = Hash[String, Array[String?]]
5+
type graph_stats = Hash[String, { average_degree: Float, starting_positions: Integer }]
6+
7+
@ranked_dictionaries: Hash[String, ranked_dictionary]
8+
@adjacency_graphs: Hash[String, adjacency_graph]
9+
@dictionary_tries: Hash[String, Trie]
10+
@graph_stats: graph_stats
11+
12+
attr_reader ranked_dictionaries: Hash[String, ranked_dictionary]
13+
attr_reader adjacency_graphs: Hash[String, adjacency_graph]
14+
attr_reader dictionary_tries: Hash[String, Trie]
15+
attr_reader graph_stats: graph_stats
16+
17+
def initialize: () -> void
18+
19+
def add_word_list: (String name, Array[String] list) -> void
20+
21+
private
22+
23+
def read_word_list: (String file) -> Array[String]
24+
25+
def build_tries: () -> Hash[String, Trie]
26+
27+
def build_trie: (ranked_dictionary ranked_dictionary) -> Trie
28+
29+
def compute_graph_stats: () -> graph_stats
30+
end
31+
end

0 commit comments

Comments
 (0)