Skip to content

Require fallback parameter in valueSet() for empty list safety#747

Merged
dahlia merged 10 commits intomainfrom
fix/issue-492-empty-list-message
Mar 28, 2026
Merged

Require fallback parameter in valueSet() for empty list safety#747
dahlia merged 10 commits intomainfrom
fix/issue-492-empty-list-message

Conversation

@dahlia
Copy link
Copy Markdown
Owner

@dahlia dahlia commented Mar 28, 2026

Summary

Fixes #492

valueSet([]) and values([]) previously returned empty message content that, when embedded in message tagged templates, collapsed surrounding prose into malformed sentences like "Expected one of ." or "Allowed values: .".

This PR makes valueSet() require a second parameter that specifies what to display when the values array is empty. The parameter can be either a simple fallback string or an options object with a required fallback field. This is a breaking change to the valueSet() API.

// Simple fallback string
message`Expected one of ${valueSet(choices, "(none)")}.`
// → "Expected one of (none)." when choices is empty

// Options object with fallback
message`Expected ${valueSet(choices, { fallback: "(none)", type: "disjunction" })}.`

// Empty fallback string produces an empty Message (same as old behavior)
message`Available: ${valueSet(branches, "")}.`

The fallback is enforced both at the type level (ValueSetOptions.fallback is a required field) and at runtime (throws TypeError for JavaScript callers or code compiled against the old signature).

For values(), which is only used internally to represent consumed tokens, the function now throws TypeError for empty arrays since an empty consumed token list is always a bug.

The git parser error messages in @optique/git have been updated to omit the "Available ..." suffix when the list is empty, and user-facing callbacks like notFound(input, available) can now use the fallback parameter directly instead of manual ternary guards: valueSet(available ?? [], "none").

Test plan

  • mise test passes across all runtimes (Deno, Node.js, Bun)
  • mise check passes (type check, lint, format, dry-run publish)
  • pnpm build under docs/ passes (Twoslash type checking for code examples)

@dahlia dahlia self-assigned this Mar 28, 2026
@dahlia dahlia added the bug Something isn't working label Mar 28, 2026
@dahlia dahlia added this to the Optique 1.0 milestone Mar 28, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 28, 2026

Codecov Report

❌ Patch coverage is 75.43860% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.33%. Comparing base (6945a5e) to head (b2463c8).
⚠️ Report is 11 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/git/src/index.ts 39.13% 14 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #747      +/-   ##
==========================================
- Coverage   91.38%   91.33%   -0.06%     
==========================================
  Files          39       39              
  Lines       20867    20901      +34     
  Branches     5689     5710      +21     
==========================================
+ Hits        19070    19089      +19     
- Misses       1749     1764      +15     
  Partials       48       48              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a breaking change to the valueSet() function, which now requires a mandatory second parameter to specify fallback text for empty input arrays. Additionally, the values() function now throws a TypeError when given an empty array. The changes include comprehensive updates to documentation, unit tests, and internal logic across multiple packages to ensure compliance with the new API requirements. I have no feedback to provide.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 32898a37-fafe-4d7c-903f-956ca602b994

📥 Commits

Reviewing files that changed from the base of the PR and between 85d8b7b and b2463c8.

⛔ Files ignored due to path filters (1)
  • deno.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • CHANGES.md
  • docs/concepts/messages.md
  • docs/integrations/git.md
  • packages/core/src/constructs.test.ts
  • packages/core/src/doc.test.ts
  • packages/core/src/message.test.ts
  • packages/core/src/message.ts
  • packages/core/src/primitives.ts
  • packages/core/src/valueparser.ts
  • packages/env/src/index.ts
  • packages/git/src/index.test.ts
  • packages/git/src/index.ts
  • packages/man/src/man.test.ts
  • packages/man/src/roff.test.ts
  • packages/zod/src/index.ts

Walkthrough

This PR changes empty-list handling for message utilities: valueSet() now requires a second parameter (either a fallback string or an options object with a required fallback: string) and, when given an empty values array, returns either an empty Message (for fallback === "") or a single text term containing the fallback. values() now throws TypeError when passed an empty array. Call sites, tests, docs, and changelog entries were updated to pass explicit fallbacks; several git-related error messages were also adjusted to omit the “Available …” suffix when no alternatives exist.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: requiring a fallback parameter in valueSet() to handle empty lists safely, addressing the core breaking change in this PR.
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation (issue #492), the solution (required fallback parameter), and implementation details across multiple files.
Linked Issues check ✅ Passed The PR fully addresses issue #492 by making valueSet() require a fallback parameter to prevent malformed sentences when embedding empty value lists, and values() now throws TypeError for empty arrays, meeting all stated objectives.
Out of Scope Changes check ✅ Passed All changes are directly in scope: valueSet() signature and implementation changes, values() error handling, test/documentation updates across all packages reflect the required fallback parameter, and git parser updates to omit empty availability lists.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-492-empty-list-message

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/concepts/messages.md`:
- Around line 361-363: The locale definition entry contains a sentence fragment;
update the second sentence in the `locale` description so it is a complete
sentence (e.g., "Defaults to the system locale.") and ensure the `locale` entry
clearly describes accepted types (string, array of strings, Intl.Locale object,
or array of Intl.Locale objects) and the default behavior; edit the `locale`
definition text to replace the fragment with the complete sentence.

In `@packages/core/src/message.test.ts`:
- Around line 1129-1141: Update the test for valueSet to assert the thrown error
message as well as the TypeError for both call sites: when calling (valueSet as
any)([]) and (valueSet as any)([], { locale: "en" }). Locate the two
assert.throws calls in the "should throw TypeError when called without fallback"
test and replace/augment them to validate the error.message (via exact string or
regex) so the test verifies the expected diagnostic text from the runtime
validation path in addition to the TypeError.
- Around line 202-207: Update the test that currently only checks the error type
for values([]) to also assert the exact diagnostic message: when calling
values([]) assert.throws should verify both TypeError and the specific
error.message (either by passing the expected message string or a RegExp) so the
test asserts the error classification and the exact validation text produced by
the values function/constructor.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d2ba95be-7da2-4616-8d92-67754c69a21c

📥 Commits

Reviewing files that changed from the base of the PR and between dd14129 and 9d9b7f8.

⛔ Files ignored due to path filters (1)
  • deno.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • CHANGES.md
  • docs/concepts/messages.md
  • docs/integrations/git.md
  • packages/core/src/constructs.test.ts
  • packages/core/src/doc.test.ts
  • packages/core/src/message.test.ts
  • packages/core/src/message.ts
  • packages/core/src/primitives.ts
  • packages/core/src/valueparser.ts
  • packages/env/src/index.ts
  • packages/git/src/index.test.ts
  • packages/git/src/index.ts
  • packages/man/src/man.test.ts
  • packages/man/src/roff.test.ts
  • packages/zod/src/index.ts

dahlia added a commit that referenced this pull request Mar 28, 2026
#747 (comment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dahlia added a commit that referenced this pull request Mar 28, 2026
Check both the error type and the diagnostic message pattern
in values() and valueSet() throw tests, so regressions in
error classification or message wording are caught.

#747 (comment)
#747 (comment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dahlia
Copy link
Copy Markdown
Owner Author

dahlia commented Mar 28, 2026

@codex review

@dahlia
Copy link
Copy Markdown
Owner Author

dahlia commented Mar 28, 2026

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a breaking change to the valueSet() function, which now requires a second parameter to specify a fallback string or options object for empty arrays. Additionally, the values() function has been updated to throw a TypeError when provided with an empty array. These changes aim to prevent malformed sentences in message templates when lists are empty. The feedback provided suggests refactoring several error message implementations in packages/git/src/index.ts to reduce code duplication by using nested message templates for conditional parts.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

ℹ️ 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".

dahlia added a commit that referenced this pull request Mar 28, 2026
Use nested message templates to avoid repeating the base error
message in both branches of the conditional.  The period is
kept outside the conditional for clarity.

#747 (comment)
#747 (comment)
#747 (comment)
#747 (comment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dahlia
Copy link
Copy Markdown
Owner Author

dahlia commented Mar 28, 2026

@codex review

@dahlia
Copy link
Copy Markdown
Owner Author

dahlia commented Mar 28, 2026

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a breaking change to the valueSet() function, which now requires a mandatory second parameter for fallback text when the input array is empty. Additionally, the values() function has been updated to throw a TypeError if provided with an empty array. These changes aim to prevent malformed error messages in CLI outputs. The feedback suggests extracting a repeated conditional message pattern in packages/git/src/index.ts into a helper function to improve maintainability and adhere to DRY principles.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ 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".

dahlia and others added 10 commits March 28, 2026 16:36
Both functions now throw TypeError when given an empty array.
Previously, empty arrays produced zero message terms, which
caused surrounding prose in message templates to collapse
into malformed sentences (e.g., "Expected one of .").

#492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Omit the "Available ..." suffix when the list is empty,
instead of passing an empty array to valueSet() which
now throws TypeError.

#492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of throwing TypeError for empty arrays, valueSet()
now requires a second parameter (a fallback string or options
with a fallback field).  When the values array is empty, the
fallback text is returned as a single text term; an empty
fallback string produces an empty Message.

This replaces the throwing approach, which would have turned
normal validation failures into runtime exceptions in user
callbacks that receive potentially-empty arrays.

#492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revise the @optique/core entry to describe the required
fallback parameter instead of the throwing approach.

#492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update Twoslash code examples and option descriptions in
docs/concepts/messages.md and docs/integrations/git.md to
use the new required fallback parameter.

#492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JavaScript callers or code compiled against the old signature
can omit the fallback parameter, producing a malformed message
term with undefined text.  The function now throws TypeError
at the call site when the fallback is missing or invalid.

#492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#747 (comment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Check both the error type and the diagnostic message pattern
in values() and valueSet() throw tests, so regressions in
error classification or message wording are caught.

#747 (comment)
#747 (comment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use nested message templates to avoid repeating the base error
message in both branches of the conditional.  The period is
kept outside the conditional for clarity.

#747 (comment)
#747 (comment)
#747 (comment)
#747 (comment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dahlia dahlia force-pushed the fix/issue-492-empty-list-message branch from 85d8b7b to b2463c8 Compare March 28, 2026 07:37
@dahlia dahlia merged commit d9b3596 into main Mar 28, 2026
6 of 8 checks passed
@dahlia dahlia deleted the fix/issue-492-empty-list-message branch March 28, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Empty list message helpers can collapse surrounding prose into malformed sentences

1 participant