Repository navigation
perf: keep rarely declared flag metadata out of every flag's table - #1470
Conversation
FlagMeta was 664 bytes per flag, nearly all of it empty for a typical
flag. Relations, deprecation, validation, bounds and spec-only metadata
now live in a FlagExtra behind `FlagMeta::extra`. Derived tables build
each flag's extras as a constant and `FlagExtra::shared` swaps an empty
one for the single NO_FLAG_EXTRA static, so a flag that declares none of
these costs a pointer instead of ~500 bytes.
Code that reads a moved field goes through `extra` (`meta.extra.deprecated`),
and hand-written tables set them inside `extra: &FlagExtra { .. }`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (8)
Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review. 📝 WalkthroughWalkthroughFlag metadata that was previously stored directly in ChangesFlag metadata consolidation
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Refactor Suggested reviewers: Merge Risk: ⚪ Minimal · up to This change shrinks each flag's metadata table by moving rarely declared settings into a separate record that flags without such settings share. Help output, shell completion, and spec emission read the same values as before. The reported unchanged snapshots and passing tests are consistent with no behavior change. No merge-blocking risk remains; code that hand-writes flag tables must nest the moved fields under 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
|
Instruction counts
No instruction-count regression above 1%. Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run. Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes. Shadow comparisonParsing
|
<!-- entire-trail-link-start --> https://entire.io/gh/jdx/usage/trails/23 <!-- entire-trail-link-end --> oxc is moving oxlint and oxfmt from bpaf to usage-rs (oxc-project/oxc#26027), and reviewers weigh the binary size usage-rs adds. This PR takes about 10 KB off each binary without changing any output. ## What changed The short (`-h`) and long (`--help`) help pages were produced by two near-identical functions. Each passed its own closures for arguments, flags and global flags, so there were about six inlined copies of the same entry logic: layout, annotations, admonitions, deprecation notes, and the `next_line_help` branch. The flattened subcommand bodies (`flatten_help`) had a short copy and a long copy of the same code again. Now: - One `page_sections(…, long)` builds both pages. It covers the version banner, about text, commands, arguments, flags, global flags, examples, `after_help` and the package footer. - One `flat_commands(…, long)` writes the flattened bodies for both pages. - Every argument or flag row is written by a single non-generic `write_row`. It takes a small `Row` (usage, help text, choices/env/defaults with the `hide_*` switches already applied, admonitions, deprecation) and a layout that says which page it is on. This is internal to `usage-argv`. No public API changes. ## Measured size oxc's PR branch was patched to use this usage branch and built with oxc's release profile (opt-level 3, fat LTO, 1 codegen unit) on linux x86_64. The numbers are the summed allocated sections of the stripped binaries. The baseline is `main` with #1470 applied. | binary | vs #1470 | overhead vs bpaf | | ------ | -------: | ---------------: | | oxlint | −10,352 B | +128,020 B (was +138,372 B) | | oxfmt | −10,480 B | +103,804 B (was +114,284 B) | Most of the saving is in `.text` (−9,136 B), with a further −1,056 B in `.eh_frame`. Help, error, spec and completion output from both binaries was compared byte for byte against the #1470 build. That comparison covered plain, `CLICOLOR_FORCE=1` and `COLUMNS=60` output: 30 of 30 cases were identical. ## Validation - `cargo test --workspace --all-features`: 2,800 tests pass, including the existing plain and coloured help snapshots, with no snapshot changes. - `cargo clippy --workspace --all-targets --all-features -- -D warnings` is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Large refactor of help layout and visibility rules; regressions in `-h` vs `--help` formatting are possible even though tests and byte comparisons reported no output changes. > > **Overview** > Refactors **usage-argv** help generation so `-h` and `--help` share one code path instead of duplicated `short_sections` / `long_sections` and `flat_commands_short` / `flat_commands_long`. > > **`page_sections(…, long)`** now builds both pages (same section order; differs via `long` for text choice, `hide_short_help` / `hide_long_help`, long about/examples/footer, etc.). **`flat_commands(…, long)`** does the same for `flatten_help` bodies. > > Argument and flag rows go through **`Row`**, **`RowLayout`**, and a single **`write_row`** (replacing repeated closures and **`flag_notes`**). **`long_annotations`** takes a **`Row`**; **`inline_environment_notes`** no longer takes a hide flag because **`Row`** already strips hidden env fields. > > Call sites (`rendered_page`, `short_help_with`, `long_help_with`, topics) all route through **`page_sections`**. Intended effect: smaller binaries (~10 KB in oxc’s measurement) with **unchanged** help output. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f1d1085. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Help pages continue to provide short and long formats, including relevant examples and explanatory content. The underlying rendering has been consolidated, with no changes to the public interface. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
<!-- entire-trail-link-start --> https://entire.io/gh/jdx/usage/trails/28 <!-- entire-trail-link-end --> oxc is moving oxlint and oxfmt from bpaf to usage-rs (oxc-project/oxc#26027), and reviewers weigh the binary size usage-rs adds. This PR takes about 3 KB more off each binary, on top of #1471, without changing any output. ## What changed To colour a help page, usage-argv looks for what the page wrote: its headings, the usage at the start of each row, and the synopsis. It colours each line that matches one of them. Those lists came from `help_structure`, which walked the command metadata a second time after the page was written. That second walk repeated every visibility filter, sort order and usage string the section writers had just produced, including a recursive copy for flattened help. Now the section writers note each heading and row usage as they write it, but only when the page is coloured, so plain pages pay nothing. The synopsis is read from the page's own `usage` section. `help_structure`, `flat_help_usages` and `flat_help_headings` are gone. As a side effect, each row's usage string is computed once instead of twice for the column width and the row text. Line matching (`styled_help`) is unchanged. It still colours author prose that happens to match a heading or a row: for example, a line reading `Flags:` in `before_help` is coloured as a heading. Colouring spans directly as the page is written would drop that, which would change coloured output, so this PR leaves it as it is. ## Measured size oxc's PR branch was patched to use this usage branch and built with oxc's release profile (opt-level 3, fat LTO, 1 codegen unit) on linux x86_64. The numbers are the summed allocated sections of the stripped binaries. | binary | vs #1470 | vs #1471 | overhead vs bpaf | | ------ | -------: | -------: | ---------------: | | oxlint | −13,304 B | −2,952 B | +125,068 B (was +138,372 B) | | oxfmt | −13,432 B | −2,952 B | +100,852 B (was +114,284 B) | Help, error, spec and completion output from both binaries was compared byte for byte against the #1470 build. That comparison covered plain, `CLICOLOR_FORCE=1` and `COLUMNS=60` output: 30 of 30 cases were identical. Separately, plain, coloured and palette-remapped short and long help was rendered for every command of every KDL spec in the repo, plus two fixtures written for this PR. That was about 3,500 pages at widths 100 and 50, and the output was identical across #1470, #1471 and this branch. ## Tests `conformance/tests/coloured_help.rs` adds coloured snapshots for cases the existing tests did not pin: - global flags - a flattened page - next-line help - a logo in the margin - a help template that reorders and splits sections - author prose that looks like a heading The snapshots were recorded on #1471 and pass unchanged here. ## Validation - `cargo test --workspace --all-features`: 2,804 tests pass. - `cargo clippy --workspace --all-targets --all-features -- -D warnings` is clean. Stacked on #1471. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches the core help assembly and colouring path, though behaviour is intended to stay byte-identical and is heavily snapshotted and regression-tested. > > **Overview** > Coloured help no longer runs a second metadata pass via **`help_structure`** (and the related **`flat_help_*`** helpers). While **`page_sections`** builds the page, it now records headings and row usage strings in **`Sections.known`** only when output is coloured; the synopsis for styling comes from the already-written **`usage`** section. > > Section writers (**`commands_section`**, **`flat_commands`**, grouped sections via **`SectionSink`**) push into that structure as they emit lines, and arg/flag usage strings are computed once for both column sizing and row text. **`styled_help`** still uses the same recognition rules (including author prose that looks like headings). > > Adds **`conformance/tests/coloured_help.rs`** with insta snapshots for globals, flattened help, next-line layout, logos, templates, and prose edge cases. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit eca35c5. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ble (#1472) <!-- entire-trail-link-start --> https://entire.io/gh/jdx/usage/trails/24 <!-- entire-trail-link-end --> oxc is moving oxlint and oxfmt from bpaf to usage-rs (oxc-project/oxc#26027), and reviewers weigh how much binary size usage-rs adds. Much of that is static metadata. Each `CommandMeta` was about 600 bytes. A derived CLI emits one for every subcommand and every flattened `Args` type, so oxlint has about 11. Nearly every field in them was empty. This applies the #1470 `FlagMeta` → `FlagExtra` split to commands. `CommandMeta` keeps only what nearly every command sets: `cmd`, `about`, `long_about`, `hide`, `args_override_self`, `flags`, `args`, `subcommands`, `groups`, `flatten_groups`. Everything else is now in a `CommandExtra` behind `CommandMeta::extra`. That covers deprecation, hidden aliases, heading and display order, surface/`available_if`, effect, mount, restart token, clause, subcommand settings, `next_line_help`/`flatten_help`, term widths, before/after help, examples, headings, outputs, `select`, and exit codes. Derived tables build each command's extras as a constant. `CommandExtra::shared` then replaces an empty one with the single `NO_COMMAND_EXTRA` static, so a command that declares none of these costs one pointer. `shared` destructures every field without `..`, so a field added later and missed in the emptiness check is a compile error, not a silently dropped declaration. A test pins the sharing. ## Size | binary | vs #1470 | overhead vs bpaf | |---|---|---| | oxlint | −3,608 B | +134,764 B (was +138,372) | | oxfmt | −1,488 B | +112,796 B (was +114,284) | How this was measured: oxc's PR branch was patched to use this usage branch and built with oxc's release profile (opt-level 3, fat LTO, 1 codegen unit) on linux x86_64. The numbers are the summed allocated sections of the stripped binaries. Most of the saving is in `.data.rel.ro` (−4,128 B oxlint, −1,824 B oxfmt). Help, error, spec and completion output from both binaries was compared byte-for-byte against the #1470 build, and all 30 cases were identical. `ArgMeta` was left alone. oxlint and oxfmt each have one positional, so splitting it would save a few hundred bytes at most. ## Migration Code that reads or builds `CommandMeta` directly has to go through `extra` for the moved fields. Derived CLIs need no changes. ```rust // before let text = meta.after_long_help; static ROOT: CommandMeta = CommandMeta { cmd: &CMD, about: Some("…"), deprecated: Some("use `new`"), ..CommandMeta::EMPTY }; // after let text = meta.extra.after_long_help; static ROOT: CommandMeta = CommandMeta { cmd: &CMD, about: Some("…"), extra: &CommandExtra { deprecated: Some("use `new`"), ..CommandExtra::EMPTY }, ..CommandMeta::EMPTY }; ``` ## Validation `cargo test --workspace --all-features` passes (2,801 tests, no snapshot changes), and `cargo clippy --workspace --all-targets --all-features -- -D warnings` is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Layout/API refactor across help, completion, and spec emission with broad test coverage; runtime CLI behavior should be unchanged if all `extra` paths were migrated consistently. > > **Overview** > Introduces **`CommandExtra`** and moves rarely set command metadata (deprecation, hidden aliases, clause/restart tokens, help layout, examples, outputs, effects, etc.) off **`CommandMeta`** behind `meta.extra`, mirroring the existing **`FlagExtra`** pattern. **`CommandMeta`** keeps the common parse/help surface (`flags`, `args`, `subcommands`, `hide`, …) so each static table is smaller; derived CLIs call **`CommandExtra::shared`** to point empty extras at a single **`NO_COMMAND_EXTRA`** static. > > All readers/writers are updated to use `extra.*`: shell **completion**, **help** rendering and usage lines, **`Spec::to_kdl`**, conformance **KDL→tables** building, **usage-dynamic** collision checks, and **derive** codegen (root/subcommand/enum variant metadata). Hand-built **`CommandMeta`** in tests and fixtures nest moved fields under `CommandExtra { .. }`. > > **Breaking for direct `CommandMeta` users only:** field access becomes `meta.extra.after_help` (etc.); behavior and emitted help/spec/completions are intended to stay the same. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a2f8dbb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Updated how command settings are organized across command definitions and related tools. Help text, examples, completion behavior, deprecation details, and other settings retain their existing behavior. * Generated commands and specifications continue to preserve their existing metadata and help-page output. * **User Impact** * No changes to command-line behavior or help-page content are indicated. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
<!-- entire-trail-link-start --> https://entire.io/gh/jdx/usage/trails/26 <!-- entire-trail-link-end --> Every `#[derive(Cli)]` / `#[derive(Args)]` emits a static `Command` and `CommandMeta` for each command, and that table data is part of the size usage-rs adds to a CLI. That size is under review in the oxlint/oxfmt migration from bpaf (oxc-project/oxc#26027). Two things made these records bigger than they needed to be: - **The clause was stored inline.** `Command::clause` held a whole `Clause` (72 bytes) and `CommandMeta::clause` held a whole `ClauseMeta` (104 bytes), whether or not the command declared a clause. Almost no command does. - **Numbers were `Option<usize>`** (16 bytes each), even though they are small positions, bounds and widths. This PR changes both: | Field | Before | After | |---|---|---| | `Command::clause` | `Option<Clause<'a>>` | `Option<&'a Clause<'a>>` | | `CommandMeta::clause` | `Option<ClauseMeta<'a>>` | `Option<&'a ClauseMeta<'a>>` | | `CommandMeta::display_order`, `ArgMeta::display_order`, `FlagExtra::display_order` | `Option<usize>` | `Option<u32>` | | `ArgMeta::{var_min, var_max}`, `FlagExtra::{var_min, var_max, value_var_min, value_var_max}` | `Option<usize>` | `Option<u32>` | | `CommandMeta::{term_width, max_term_width}` | `Option<usize>` | `Option<u16>` | Record sizes on x86_64: `Command` goes from 184 to 120 bytes, `CommandMeta` from 600 to 472, and `ArgMeta` from 480 to 456. `Flag` (144) and `FlagMeta` (168) are unchanged. When a declared value doesn't fit, the derive and the conformance table builder saturate it instead of wrapping, which matches how the parse table's `var_max` already worked. A `display_order` above `u32::MAX` sorts last, a bound that large still means "no practical limit", and a terminal width above 65535 still never wraps. `term_width = 0` still turns wrapping off. ### Measured effect oxc's PR branch was patched to build against this branch, using oxc's release profile (opt-level 3, fat LTO, 1 codegen unit) on linux x86_64. The numbers below are summed allocated sections of the stripped binaries, compared with #1470: | Binary | Total | `.data.rel.ro` | `.text` | Other | |---|---|---|---|---| | oxlint | **−2,912 B** | −2,304 | −512 | −96 | | oxfmt | **−1,744 B** | −1,056 | −528 | −160 | This brings the overhead compared with bpaf from +138,372 B to +135,460 B for oxlint, and from +114,284 B to +112,540 B for oxfmt. The small `.rodata` deltas partly come from differences in embedded source paths between the two builds. Help, error, `--version`, spec, and completion output from both binaries was compared byte-for-byte with the #1470 build (30 invocations), and all of it is identical. ### Migration Hand-written tables need to borrow the clause: ```rust // before static RUN: Command = Command { clause: Some(TASKS), ..Command::EMPTY }; static META: CommandMeta = CommandMeta { cmd: &RUN, clause: Some(ClauseMeta { /* ... */ }), ..CommandMeta::EMPTY }; // after static RUN: Command = Command { clause: Some(&TASKS), ..Command::EMPTY }; static META: CommandMeta = CommandMeta { cmd: &RUN, clause: Some(&ClauseMeta { /* ... */ }), ..CommandMeta::EMPTY }; ``` Integer literals such as `display_order: Some(3)` need no change. Code that reads these fields as `usize` needs a conversion, for example `meta.display_order.map(|o| o as usize)`. `Event::ClauseSeparator` still carries the `Clause` by value. Code generated by `#[derive(...)]` needs no changes. ### Validation `cargo test --workspace --all-features` passes (2,800 tests) with no snapshot changes, and `cargo clippy --workspace --all-targets --all-features -- -D warnings` is clean. The parser only follows the clause reference when the command declares a clause. For every other command, the check is a null test. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches core parser/spec metadata layout and clause handling across derive and table builders; behavior is meant to be equivalent with saturation edge cases, but any hand-maintained `Command` tables must be updated. > > **Overview** > Shrinks the static `Command` / `CommandMeta` tables that every `#[derive(Cli)]` emits by storing **clauses behind references** (`Option<&Clause>` / `Option<&ClauseMeta>`) instead of inlining large structs on almost every command, and by narrowing metadata integers (`display_order` and variadic bounds to **`u32`**, terminal widths to **`u16`**) so each record takes less space in the binary. > > The parser, help renderer, completion fixtures, **derive codegen**, and **conformance** spec→table builder are updated to use the new shapes (including `usize` conversions at use sites). Oversized attribute or spec values **saturate** to `u32::MAX` / `u16::MAX` instead of wrapping—covered by new tests on 64-bit targets. Hand-written static tables must pass `&TASKS` / `&ClauseMeta { … }` for clause fields; generated code and observable CLI output are intended to stay the same aside from binary size. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 55efdcb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
<!-- entire-trail-link-start --> https://entire.io/gh/jdx/usage/trails/16 <!-- entire-trail-link-end --> ### 🐛 Bug Fixes - **(help)** colour only the structure the renderer writes by [@jdx](https://github.com/jdx) in [#1480](#1480) ### ⚡ Performance - shrink the help and completion code usage-rs adds to a binary by [@jdx](https://github.com/jdx) in [#1469](#1469) - keep rarely declared flag metadata out of every flag's table by [@jdx](https://github.com/jdx) in [#1470](#1470) - write help rows through one renderer for both help pages by [@jdx](https://github.com/jdx) in [#1471](#1471) - collect coloured-help structure while the page is written by [@jdx](https://github.com/jdx) in [#1476](#1476) - shrink the per-CLI code the derive generates for binding by [@jdx](https://github.com/jdx) in [#1477](#1477) - shrink the parse-error renderer usage-rs adds to a binary by [@jdx](https://github.com/jdx) in [#1478](#1478) - shrink the shell-completion code a derived CLI carries by [@jdx](https://github.com/jdx) in [#1475](#1475) - keep rarely declared command metadata out of every command's table by [@jdx](https://github.com/jdx) in [#1472](#1472) - shrink the command and metadata tables a derive emits by [@jdx](https://github.com/jdx) in [#1474](#1474) ### 🔍 Other Changes - **(ci)** comment on a discussion when the PR implementing it merges by [@jdx](https://github.com/jdx) in [#1460](#1460) ### 📦️ Dependency Updates - update jdx/packslip action to v1.2.0 by [@renovate[bot]](https://github.com/renovate[bot]) in [#1466](#1466) - update zizmorcore/zizmor-action action to v0.6.4 by [@renovate[bot]](https://github.com/renovate[bot]) in [#1465](#1465) - update module github.com/urfave/cli/v3 to v3.12.0 by [@renovate[bot]](https://github.com/renovate[bot]) in [#1467](#1467) - update aube to v2.3.0 by [@jdx](https://github.com/jdx) in [#1473](#1473)
https://entire.io/gh/jdx/usage/trails/22
Every flag in a usage-rs CLI gets a static
FlagMetarecord in the binary. Before this change it was 664 bytes: 58 fields, nearly all empty for a typical flag, which usually declares only help, a heading and maybe a default or choices. With oxlint's 47 flags plus the built-in help/version entries, that was ~35 KB of mostly zeros. After #1469, it was the largest remaining part of what usage-rs adds to oxlint and oxfmt (oxc-project/oxc#26027).What changed
The 35 rarely declared fields move into a new
FlagExtrastruct behindFlagMeta::extra. They cover:conflicts,requires*,required_if*,required_unless*,overrides,exclusive,default_if;deprecated_env;delimiter;display_order,admonitions,value_names,env_fallback,hide_env_values;choice_aliases,choice_details,complete_type,surface,available_if,effect.FlagMetaitself is now 168 bytes.A flag that declares none of these points at one shared static,
NO_FLAG_EXTRA. The derive can't always tell at expansion time whether a flag's extras are empty, because choice aliases can come from aValueEnum's trait constants. So it builds each flag's extras as aconst, and theconst fnFlagExtra::sharedswaps an empty one for the shared static at compile time.shareddestructures every field without.., so adding a field toFlagExtrawithout updating the check is a compile error, not silently dropped metadata.For code that uses these types directly
Derived CLIs need no changes. Code that reads a moved field goes through
extra:Hand-written tables set the moved fields in a nested literal. In a
staticthe reference is promoted, as with the table's other slices:FlagMeta::EMPTYalready points at the shared instance, so..FlagMeta::EMPTYkeeps working for flags with no extras.Measured effect
Measured on oxc's PR branch, with its usage crates patched to this branch, using oxc's release profile (
opt-level = 3, fat LTO) on linux x86_64. Numbers are summed allocated sections of the stripped binaries, compared with #1469 alone:.data.rel.ro.rela.dyn(one pointer per flag).text+.rodataTogether with #1469, usage-rs's overhead over bpaf drops from +219,300 B to +138,372 B on oxlint (−37%) and from +169,148 B to +114,284 B on oxfmt (−32%).
Output is unchanged. On both oxc binaries, I compared help (plain and coloured),
--version, error messages,__usage_spec__and completions before and after, and all 24 cases were byte-identical. The workspace suite passes (2,800 tests), with no snapshot changes, and clippy is clean. A new test pins that empty extras resolve to the shared instance and that anything declared, even a lonebool, keeps its own copy.ArgMetaandCommandMetakeep their layout. oxlint has one positional and ten command records, so the same split would save a few KB at most.🤖 Generated with Claude Code
Note
Medium Risk
Wide refactor of flag metadata layout and every consumer path (help, completion, spec emission, derive); behavior should be identical but any missed
extra.migration would be a subtle runtime bug.Overview
Shrinks per-flag static metadata by moving ~35 rarely set fields (relations, deprecation, validation, hidden aliases, spec-only choice/completion data, effects, etc.) from inline
FlagMetainto a newFlagExtra, referenced byFlagMeta::extra. Common flags with no extras point at a single sharedNO_FLAG_EXTRA; derive and table builders construct a per-flagFlagExtraand runFlagExtra::sharedso empty extras collapse to that static at compile time.Call sites in completion, help rendering, KDL/spec emission, conformance table building, and tests now read those fields through
meta.extra.*instead of onFlagMetadirectly.help_headingstays onFlagMeta. Hand-writtenFlagMetaliterals nest anextra: &FlagExtra { … }block; generated CLIs are unchanged at the source level.No intended behavior change—same help, completions, and spec output—with the goal of much smaller
.data.rel.roin large CLIs.Reviewed by Cursor Bugbot for commit e3c76b6. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit