Add Steep type checking for Config
Annotates Config, SentryConfig, and DefaultUser with rbs-inline
types. Adds a Steepfile scoped to config.rb and a `rake check`
task that generates RBS then runs `steep check`. CI runs it
alongside tests.
rbs-inline can't pick up methods defined inside a Data.define
block, so DefaultUser.parse is reopened in a separate class body.
Closes #7
Assisted-by: Claude Opus 4.6 via Claude Code
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1e087cf..9117fce 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- - run: bundle exec rake test
+ - run: bundle exec rake test check
build:
needs: test
diff --git a/.gitignore b/.gitignore
index 2e274d9..2ba718e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
db/*.db
+sig/generated/
diff --git a/Rakefile b/Rakefile
index 0c227ff..e267267 100644
--- a/Rakefile
+++ b/Rakefile
@@ -126,4 +126,10 @@ namespace :snapshots do
end
end
-task default: %i[ test binstubs ]
+desc "Generate RBS from inline annotations and run Steep type checker"
+task :check do
+ sh "rbs-inline --output lib/"
+ sh "steep check"
+end
+
+task default: %i[ test check binstubs ]
diff --git a/Steepfile b/Steepfile
new file mode 100644
index 0000000..0578d34
--- /dev/null
+++ b/Steepfile
@@ -0,0 +1,4 @@
+target :lib do
+ signature "sig"
+ check "lib/ketchup/config.rb"
+end
diff --git a/lib/ketchup/config.rb b/lib/ketchup/config.rb
index b461b39..fd6c128 100644
--- a/lib/ketchup/config.rb
+++ b/lib/ketchup/config.rb
@@ -1,16 +1,36 @@
+# rbs_inline: enabled
# frozen_string_literal: true
-Config = Data.define(:database_url, :sentry, :default_user, :commit_sha, :change_id, :build_date)
+Config = Data.define(
+ :database_url, #: String
+ :sentry, #: SentryConfig?
+ :default_user, #: DefaultUser?
+ :commit_sha, #: String?
+ :change_id, #: String?
+ :build_date, #: String?
+)
class Config
- SentryConfig = Data.define(:dsn, :env)
- DefaultUser = Data.define(:login, :name) do
+ SentryConfig = Data.define(
+ :dsn, #: String
+ :env, #: String?
+ )
+
+ DefaultUser = Data.define(
+ :login, #: String
+ :name, #: String
+ )
+
+ # Reopened because rbs-inline ignores methods defined inside Data.define blocks.
+ class DefaultUser
+ #: (String) -> DefaultUser
def self.parse(value)
- login, name = value.split(":", 2)
+ login, name = value.split(":", 2) #: [String, String?]
new(login: login, name: name || login)
end
end
+ #: () -> String
def to_s
parts = ["database=#{database_url}"]
parts << "sentry=#{sentry.env || "on"}" if sentry
@@ -21,6 +41,7 @@ class Config
"Config(#{parts.join(", ")})"
end
+ #: (?Hash[String, String] env) -> Config
def self.from_env(env = ENV)
sentry_dsn = env["SENTRY_DSN"]
default_user = env["DEFAULT_USER"]