Skip to content

ruff_ruby_spo: record each Rails enum in the form the source wrote it - #137

Merged
AdaWorldAPI merged 5 commits into
mainfrom
ccr-8062bcc7-l7lkz9-r1
Nov 8, 2026
Merged

AdaWorldAPI merged 5 commits into
mainfrom
ccr-8062bcc7-l7lkz9-r1

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Summary

The Ruby walker kept a Rails enum's variants only for the hash form, enum :status, { open: 0, closed: 1 }. The other forms lost them:

  • The array form enum :status, [:open, :closed] (where the position is the stored value), a constant argument enum :status, STATUSES, and a call with no variant argument all produced the same empty option list.
  • The legacy keyword form enum status: { open: 0 }, _prefix: true produced no declaration at all.
  • A trailing option such as prefix: true after an array was captured as if it were a variant.

Model::enum_forms now holds one EnumForm per enum column, alongside the legacy AttrDecl, which is unchanged. EnumVariants records the form as written:

Variant Form What it keeps
Hash hash form each key with its stored value, verbatim
Array array form the keys; the position is the stored value
Unresolved a constant or other expression the expression, rendered verbatim
Missing no variant argument nothing

Rails enum options are kept in EnumForm::options and never read as variants: prefix, suffix, scopes, default, validate, and the legacy forms _prefix, _suffix, _scopes, _default.

Rails 7.1 also allows enum :status, open: 0, closed: 1, where the keyword arguments are the variants. There, the option keys are split off and the remaining pairs become Hash variants. In that form a variant literally named prefix, suffix, scopes, default or validate is read as an option.

The stored values stay in this IR. OGAR's ogar-from-ruff reads the keys only and never uses a stored value as a label. Duplicate keys are kept as written, because refusing them is the consumer's job (OGAR maps them to EnumSource::Invalid).

Model::enum_forms uses #[serde(default, skip_serializing_if = "Vec::is_empty")], so dumps without the field still deserialize. EnumForm and EnumVariants are #[non_exhaustive], with an EnumForm::new constructor.

Test plan

  • cargo test -p ruff_spo_triplet -p ruff_ruby_spo passes. New tests:
    • one per form;
    • legacy single-column and multi-column calls;
    • the option-capture regression;
    • trailing options after a hash;
    • duplicate keys;
    • a model with no enums;
    • forms flowing through a class reopen;
    • a pin that the legacy AttrDecl is unchanged;
    • serde round trip and old-JSON compatibility.
  • cargo clippy -p ruff_spo_triplet -p ruff_ruby_spo --all-targets --all-features -- -D warnings is clean.
  • cargo check --all-targets passes for ruff_csharp_spo, ruff_python_spo, ruff_spo_address and ruff_sqlalchemy_spo.
  • Three disable runs, each applied on top of the commit, turn tests red:
    • classifying an array as unresolved;
    • reading trailing options as variants;
    • dropping the legacy keyword form.

Generated by Claude Code

The Ruby walker kept a Rails enum's variants only for the hash form
`enum :status, { open: 0 }`. The array form, a constant argument and a
call with no variant argument all produced the same empty option list,
and the legacy keyword form `enum status: { open: 0 }` produced no
declaration at all. A trailing option such as `prefix: true` after an
array was captured as if it were a variant.

`Model::enum_forms` now carries one `EnumForm` per enum column beside
the unchanged legacy `AttrDecl`. `EnumVariants` distinguishes `Hash`
(key and physical value as written), `Array` (keys; the position is the
physical value), `Unresolved` (a constant or other expression, rendered
verbatim) and `Missing`. Rails enum options (`prefix`, `suffix`,
`scopes`, `default`, `validate` and their legacy underscore forms) are
kept separately and are never read as variants. The physical values
stay in this IR; OGAR never receives them as labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMWgnUU9g7Ldyn6sbk
@coderabbitai

coderabbitai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 829979e3-cffc-490f-8249-caecb8255567
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review November 7, 2026 23:44
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-08T00:09:02.146914Z 8fdb7c6 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 29a4ace7ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/ruff_ruby_spo/src/walk.rs Resolved
Comment thread crates/ruff_ruby_spo/src/walk.rs Resolved
Comment thread crates/ruff_ruby_spo/src/walk.rs Resolved
Three review findings on the typed enum forms:

- `enum :status, ["open", "closed"]` stored the keys with their quotes,
  so a string array produced different keys from the equivalent symbol
  array. Array elements now unwrap `"str"` the way hash keys do.
- An unresolved argument such as `STATUSES.merge(extra_statuses)` was
  stored as the renderer's `<expr>` placeholder, so different expressions
  were indistinguishable. The walker now receives the source it parsed
  and keeps the exact text; callers without the source (the event arm)
  fall back to the renderer.
- A hash, keyword or array literal that splices in values
  (`{ **BASE, archived: 2 }`, `[*BASE, :archived]`) was published as a
  partial list missing the spliced values. Such a literal is now
  `Unresolved`, with its source text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMWgnUU9g7Ldyn6sbk

Copy link
Copy Markdown
Owner Author

@codex review

The three findings on 29a4ace are addressed in bd202d7.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd202d7b7a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/ruff_ruby_spo/src/walk.rs Resolved
Comment thread crates/ruff_ruby_spo/src/walk.rs Resolved
Comment thread crates/ruff_ruby_spo/src/walk.rs Resolved
The walker split keyword arguments with one option-key list for every
call form, which disagrees with Rails' `enum(name = nil, values = nil,
**options)`:

- With a column name and no positional values, Rails reads every keyword
  as a variant, so `enum :status, open: 0, default: 1` declares a variant
  `default`; the walker had moved it to the options.
- The legacy form slices off exactly `_prefix`, `_suffix`, `_scopes`,
  `_default` and `_instance_methods`; the walker dropped a `default:`
  column and turned `_instance_methods:` into a column.
- A string column name (`enum "status", ..`) produced no form at all.
- Hash values were rendered by the lossy argument renderer, so
  `Status::OPEN.to_i` became a placeholder.

The modern form now treats keywords as variants when no values argument
is present and as options otherwise; the legacy form uses only the
underscored option keys; a string column name is accepted; and hash
values and option values keep their exact source text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMWgnUU9g7Ldyn6sbk

Copy link
Copy Markdown
Owner Author

@codex review

The three findings on bd202d7 are addressed in ae6b752. Enum argument classification now follows Rails' enum signature.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae6b752d4c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/ruff_ruby_spo/src/walk.rs Resolved
A hash key or array element that needs evaluation, such as
`"open".freeze`, a constant or an interpolated string, was published
as a resolved variant whose key was the renderer's `<expr>` placeholder
or the constant's name. Hash and array literals now resolve only when
every key is a symbol or a plain string literal and nothing is splatted
in; otherwise the whole argument is `Unresolved` with its source text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMWgnUU9g7Ldyn6sbk

Copy link
Copy Markdown
Owner Author

@codex review

The computed-key finding on ae6b752 is addressed in 2953c47.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2953c47ebf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/ruff_ruby_spo/src/walk.rs Resolved
A keyword splat among enum options (`**ENUM_OPTIONS`) was discarded, so the
form looked complete. EnumForm now carries unresolved_options with each
splat's source text, for the modern and legacy forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMWgnUU9g7Ldyn6sbk

Copy link
Copy Markdown
Owner Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8fdb7c67fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let at = anchor(stmt);
let mut decls = Vec::new();
crate::walk::walk_class_body(stmt, &mut decls);
crate::walk::walk_class_body(stmt, None, &mut decls);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 1. Thread source text through the Rails event arm

For callers of events::source_ore, an enum such as enum :status, STATUSES.merge(extra) still produces EnumVariants::Unresolved("<expr>"), and hash values containing method calls likewise become "<expr>". Unlike the corrected model-extraction path, this newly updated event call explicitly passes None, forcing source_text to use the lossy renderer even though source_ore has the original source. Consequently, the new EnumForm payloads in ClassOre::declarations lose expressions and can disagree with the model extracted from the same file. Thread the source through the event collection path and pass it to this walker.

Useful? React with 👍 / 👎.

@AdaWorldAPI
AdaWorldAPI merged commit 9752608 into main Nov 8, 2026
36 of 37 checks passed
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.

2 participants