Skip to content

Modernize chronic for the current Ruby ecosystem#433

Open
dior001 wants to merge 1 commit into
mojombo:masterfrom
dior001:necro-ruby/modernize
Open

Modernize chronic for the current Ruby ecosystem#433
dior001 wants to merge 1 commit into
mojombo:masterfrom
dior001:necro-ruby/modernize

Conversation

@dior001

@dior001 dior001 commented Jul 23, 2026

Copy link
Copy Markdown

NecroRuby

NecroRuby has revived chronic

NecroRuby is a bot that brings quality open-source Ruby libraries
up-to-date with the modern Ruby ecosystem — upgrading dependencies,
restoring test coverage, tightening security, and improving documentation
for gems whose last release is over a year old.

NecroRuby is a fully autonomous process and is capable of mistakes. If you
disagree with any of these changes, just say so on this PR (or close it) and
NecroRuby will move on. If you have questions, ask here — NecroRuby monitors
this PR and will respond.

Modernized and tested on Ruby 4.0.6, the latest Ruby release.

Chronic Modernization Report

This pull request modernizes Chronic (natural-language date/time parser)
so it installs, runs, tests clean, and lints clean on Ruby 4.0.6 while
keeping the public API and parsing behavior unchanged.

Everything below was verified on Ruby 4.0.6 (ruby -vruby 4.0.6).

Summary

Area Before After
Runs on Ruby 4.0.6 ❌ (test suite could not even load)
Test suite 184 tests 218 tests, 1242 assertions, 0 failures
Line coverage 92.32% 100.0% (enforced via SimpleCov minimum_coverage)
RuboCop not configured, 1500+ offenses 0 offenses
Dependency audit (bundler-audit) not run 0 vulnerabilities
Declared Ruby floor none required_ruby_version >= 3.0, .ruby-version = 4.0.6

1. Dependencies

Runtime and development dependencies were moved to current, maintained
versions and the gemspec was cleaned of removed/deprecated RubyGems APIs.

Gem Before After Notes
numerizer (runtime) ~> 0.2 ~> 0.2 Kept — 0.2.0 is the current, maintained release; declared with the modern add_dependency.
rake >= 10 >= 13.0
minitest ~> 5.0 >= 5.19 Now resolves to minitest 6.x.
simplecov ~> 0 ~> 0.22
activesupport >= 4.0 >= 6.0 Used only by the time-zone specs.
rubocop ~> 1.79 (added) Linting/formatting.
bundler-audit ~> 0.9 (added, Gemfile dev/test group) Dependency CVE auditing.

Gemspec hygiene:

  • Removed s.rubyforge_project= — the setter was removed from modern RubyGems
    and made the gemspec unloadable.
  • Removed s.test_files= — deprecated and warned on every gem operation.
  • Switched add_runtime_dependencyadd_dependency.
  • Added required_ruby_version = '>= 3.0', require_paths, and a metadata
    block (source_code_uri, changelog_uri, bug_tracker_uri,
    rubygems_mfa_required).
  • s.files now splits on "\n" instead of the $/ global.

bundle install and gem build chronic.gemspec both succeed cleanly.

2. Ruby 4.0.6 compatibility fixes

  • MiniTestMinitest. minitest 6 removed the long-deprecated
    MiniTest constant; the test base class now subclasses Minitest::Test.
  • Frozen string literals. Added # frozen_string_literal: true to every
    Ruby file. The #to_s helpers built strings by mutating string literals
    (super << '-day', '(' << ... << ')'), which raises under frozen string
    literals; these were rewritten with interpolation ("#{super}-day").
  • Removed dead legacy branches:
    • Chronic.construct dropped the unreachable RUBY_VERSION.to_f < 1.9
      condition (kept the ActiveSupport::TimeZone/#local path).
    • Span dropped alias cover? include? if RUBY_VERSION =~ /^1.8/
      (Range#cover? is native on all supported Rubies).
    • Parser#pre_normalize dropped a dead else branch in the two-digit-year
      expansion (a two-digit capture is always a plausible year).
  • Test harness / coverage loading. Modern Bundler evaluates the gemspec
    (which requires chronic/version), so Chronic is already defined before
    the tests run. The old unless defined? Chronic guard in test/helper.rb
    therefore skipped loading the real library. The helper now requires
    chronic unconditionally, and SimpleCov.start runs before the library is
    loaded so coverage is actually measured.
  • Rakefile now uses Rake::TestTask instead of hand-rolled Dir.glob
    requires (the old version pre-required chronic, which defeated coverage).
  • Fixed a mismatched-indentation warning (-w) in tags/scalar.rb.

Explicit target declaration

  • .ruby-version4.0.6.
  • required_ruby_version>= 3.0 (the genuine floor: no 3.x/4.0-only
    syntax is used, and the suite passes across the range).
  • CI matrix updated (.github/workflows/test.yaml) to 3.0, 3.1, 3.2, 3.3, 3.4, 4.0 plus JRuby/TruffleRuby, and two new jobs were added: RuboCop
    and bundler-audit.

3. Tests & coverage

  • Coverage rose from 92.32% → 100% line coverage, now enforced by
    SimpleCov.minimum_coverage line: 100 in test/helper.rb (branch coverage
    is also tracked for information; the ~12% of unhit branches are unreachable
    implicit else arms of case pointer statements).
  • New test files:
    • test/test_coverage.rb — tokenizer/token/season/date helpers, the
      abstract-method guards (Tag.scan, Repeater#width, Definitions),
      handler pattern errors, the unknown-grabber guard, and every handler's
      "invalid date" rescue path (driven by a stub time class whose
      constructors raise).
    • test/test_to_s.rb — the #to_s debug representation of every Tag and
      Repeater subclass.
    • test/test_repeater_branches.rb — repeater widths, offsets, and the
      less-common this/next pointers.
  • Re-enabled TestChronic#test_activesupport, which had been commented out
    with a stale note about a MiniTest 4.2 conflict; it now runs against
    ActiveSupport 8.x and exercises the Chronic.time_class = Time.zone path.

The full suite passes in multiple time zones (TZ=UTC, default).

4. Documentation

  • README refreshed: added a Requirements section (Ruby ≥ 3.0), a Bundler
    install snippet, a Development section (test/lint/audit commands),
    modernized all :key => value examples to key: syntax, and turned the
    time-zone example into runnable Ruby.
  • Added inline class/method documentation to the public helper classes that
    lacked it (MiniDate, Season, Chronic::Date, Chronic::Time, Token,
    Tokenizer, Handler) and documented the behavior-affecting code changes.

5. Lint / formatting

  • Added a pragmatic .rubocop.yml (TargetRubyVersion: 3.0, NewCops: enable). Complexity/size metrics are disabled because Chronic's
    tokenizer/handlers are inherently long and branchy — enforcing them would
    require a rewrite out of scope for a compatibility pass. A few cops that
    would force breaking API changes (Style/OptionalBooleanParameter,
    Naming/PredicatePrefix) or noisy churn (Naming/VariableNumber) are
    disabled with rationale in the config.
  • Ran safe and unsafe autocorrect, then fixed the remainder by hand
    (converted two @@class_vars to class instance variables, documented one
    intentional Lint/MissingSuper, broke a long line). bundle exec rubocop
    now reports no offenses, and the suite still passes at 100% coverage
    after autocorrection.
  • No JavaScript exists in the repository, so no JS linter was required.

6. Security

  • bundle exec bundle-audit check --update against the current
    ruby-advisory-db (1,224 advisories): no vulnerabilities found.
  • Brakeman is intended for Rails applications (controllers/models/routes) and
    is not applicable to a pure parsing library; it was not added.
  • Manually reviewed the reflection points (Chronic.const_get, Object#send
    in handler.rb/dictionary.rb/tags/repeater.rb): all operate on
    internal, developer-defined constant/method names from the handler
    definitions and fixed lookup tables — never on user-supplied input — so
    there is no injection surface.

Design notes

  • Behavior is intentionally unchanged: the parser logic, option handling, and
    return types are identical. All edits are compatibility, formatting,
    documentation, or test additions.
  • Coverage is enforced on lines (the universal metric) rather than
    branches; the residual branches are defensive case/when arms that cannot
    be reached with the values the parser actually produces.

🤖 Opened automatically by NecroRuby, an UpWoof.ai service.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant