Skip to content

Allow ParseOptions as the second argument of getDocPage*() helpers#739

Merged
dahlia merged 6 commits intomainfrom
fix/issue-480-getdocpage-options
Mar 26, 2026
Merged

Allow ParseOptions as the second argument of getDocPage*() helpers#739
dahlia merged 6 commits intomainfrom
fix/issue-480-getdocpage-options

Conversation

@dahlia
Copy link
Copy Markdown
Owner

@dahlia dahlia commented Mar 26, 2026

Summary

getDocPage(), getDocPageSync(), and getDocPageAsync() accepted an optional args array as their second parameter and an optional ParseOptions as their third. Because args was optional, it was natural to expect getDocPage(parser, { annotations }) to work, but the implementation always treated the second positional argument as args, silently ignoring the annotations. Users had to write getDocPage(parser, [], { annotations }) instead.

This PR replaces the separate args? and options? parameters with a single union parameter argsOrOptions?: readonly string[] | ParseOptions, disambiguated at runtime via Array.isArray(). The same pattern is already used by runParser() in facade.ts. This change also reduces getDocPage()'s overloads from six to three (one per mode), and eliminates overloads entirely for getDocPageSync() and getDocPageAsync().

All of the following call shapes now work correctly at both the type level and runtime:

getDocPage(parser);
getDocPage(parser, ["--verbose"]);
getDocPage(parser, { annotations: { [key]: value } });
getDocPage(parser, ["--verbose"], { annotations: { [key]: value } });
getDocPage(parser, undefined, { annotations: { [key]: value } });

// Wrapper forwarding also compiles without casts:
function wrapper(argsOrOptions?: readonly string[] | ParseOptions) {
  return getDocPageSync(parser, argsOrOptions);
}

Fixes #480.

Test plan

  • New tests verify annotations are actually injected (not just that the call doesn't crash) by using a custom parser whose getDocFragments reads annotations from state and embeds them in the footer
  • Regression tests for the (parser, undefined, { annotations }) three-argument forwarding pattern
  • Type-level test for ParseOptions | undefined as the second argument
  • Composability test: a wrapper forwarding readonly string[] | ParseOptions compiles and works
  • mise test passes across all runtimes (Deno, Node.js, Bun)

dahlia and others added 5 commits March 26, 2026 22:47
getDocPage(), getDocPageSync(), and getDocPageAsync() silently
misinterpreted a ParseOptions object as the args array when passed
as the second argument. All three functions now accept ParseOptions
as the second argument directly via new overloads and runtime
Array.isArray() disambiguation.

#480

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The disambiguation logic dropped optionsParam whenever the second
argument was undefined, so getDocPage(parser, undefined, { annotations })
silently lost the annotations.  Use a three-way condition: array → args;
non-null non-array → ParseOptions; undefined → fall through to the
third argument.

#480

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The new options-as-second-argument overloads required ParseOptions
(non-optional), so callers forwarding an optional options variable
(ParseOptions | undefined) got a type error. Make the parameter
optional in all five new overloads.

#480

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace two overload groups (options-only and args+options) with a
single union parameter readonly string[] | ParseOptions per function.
This makes the API fully composable: wrappers forwarding the union
type no longer need casts or type guards.

getDocPageSync and getDocPageAsync lose their overloads entirely;
getDocPage drops from 6 overloads to 3 (one per mode).

#480

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the usability and robustness of the getDocPage(), getDocPageSync(), and getDocPageAsync() helper functions. It resolves a long-standing issue where ParseOptions objects, when passed as the second argument, were incorrectly treated as command-line arguments, leading to unexpected behavior. By introducing a unified argsOrOptions parameter, the API now correctly disambiguates between argument arrays and parse options, allowing for more flexible and intuitive function calls. This change also streamlines the function signatures by reducing the number of overloads, making the API cleaner and easier to maintain.

Highlights

  • API Refinement: Refactored getDocPage(), getDocPageSync(), and getDocPageAsync() functions to correctly accept ParseOptions as their second argument, improving API ergonomics and type safety.
  • Bug Fix: Fixed a bug where ParseOptions passed as the second argument was silently misinterpreted as an args array, leading to incorrect behavior.
  • Simplified Overloads: Simplified the API by reducing the number of overloads for getDocPage() from six to three, and eliminating overloads entirely for getDocPageSync() and getDocPageAsync().
  • Intuitive Call Patterns: Enabled more intuitive call patterns, such as getDocPage(parser, { annotations }), without requiring an explicit empty args array.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

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

codecov bot commented Mar 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.27%. Comparing base (edc4d96) to head (eb3760b).
⚠️ Report is 7 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #739   +/-   ##
=======================================
  Coverage   92.26%   92.27%           
=======================================
  Files          39       39           
  Lines       20375    20389   +14     
  Branches     5646     5656   +10     
=======================================
+ Hits        18800    18814   +14     
  Misses       1530     1530           
  Partials       45       45           

☔ 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 addresses an issue in getDocPage(), getDocPageSync(), and getDocPageAsync() where ParseOptions were misinterpreted when passed as the second argument. The functions have been updated to directly accept ParseOptions in this position, simplifying the process of passing annotations. New test cases validate this behavior, and CHANGES.md has been updated. The review feedback suggests refactoring getDocPageAsync and getDocPage to consolidate argument and option resolution logic, making the implementations more concise and maintainable. Additionally, getDocPageAsync should ensure synchronous results are wrapped in Promise.resolve() to adhere to the require-await lint rule.

@dahlia
Copy link
Copy Markdown
Owner Author

dahlia commented Mar 26, 2026

@codex review

@dahlia
Copy link
Copy Markdown
Owner Author

dahlia commented Mar 26, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Walkthrough

This PR updates getDocPage, getDocPageSync, and getDocPageAsync so their second parameter accepts a union readonly string[] | ParseOptions. Implementations now detect at runtime whether the second argument is an args array or a ParseOptions object; when a ParseOptions object is provided as the second argument, args is treated as [] and the provided options are used. JSDoc was updated, tests were added to cover the new call shapes (including undefined args and type-check cases), and CHANGES.md was updated to document the behavior change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: allowing ParseOptions as the second argument of getDocPage* helpers, which directly addresses the linked issue #480.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, explaining the problem, solution, and test plan for the union parameter change.
Linked Issues check ✅ Passed The PR fully addresses issue #480 by implementing runtime and type-level disambiguation of the second parameter as a union of string[] | ParseOptions, enabling the natural getDocPage(parser, { annotations }) call pattern.
Out of Scope Changes check ✅ Passed All changes are scope: CHANGES.md documents the fix, parser.ts implements the union parameter handling, and parser.test.ts adds comprehensive tests covering the new functionality and edge cases.

✏️ 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-480-getdocpage-options

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: 1

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

Inline comments:
In `@packages/core/src/parser.ts`:
- Around line 879-907: getDocPageAsync currently calls getDocPageSyncImpl()
eagerly (via Promise.resolve(...)) and manually checks parser.$mode, which can
throw synchronously and violates the dispatchByMode guideline; refactor
getDocPageAsync to use dispatchByMode(parser, () => getDocPageSyncImpl(parser,
args, opts), () => getDocPageAsyncImpl(parser, args, opts)) so the sync path is
lazily invoked (or make the wrapper async and use dispatchByMode with functions)
and remove manual $mode checks and type assertions, mirroring the parse() /
suggest() pattern so all thrown errors become promise rejections.
🪄 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: 3ac777e7-cbab-4b28-92a8-9549c6847aad

📥 Commits

Reviewing files that changed from the base of the PR and between edc4d96 and 40b1492.

📒 Files selected for processing (3)
  • CHANGES.md
  • packages/core/src/parser.test.ts
  • packages/core/src/parser.ts

@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".

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: 1

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

Inline comments:
In `@packages/core/src/parser.ts`:
- Around line 971-995: The function getDocPage currently branches on
parser.$mode and uses type assertions; replace that manual branching with a call
to dispatchByMode(parser, handlers) so the mode-dispatched return types are
handled for you: call dispatchByMode with a sync handler that returns
getDocPageSyncImpl(parser as Parser<"sync",...,>, args, options) and an async
handler that returns getDocPageAsyncImpl(parser, args, options); ensure both the
array-argument and no-argument paths delegate into dispatchByMode (pass [] when
argsOrOptions is not an array) and remove the explicit parser.$mode checks and
type-asserting branches.
🪄 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: c19d3a6e-6957-4a6a-bf75-eebdb78e39b9

📥 Commits

Reviewing files that changed from the base of the PR and between edc4d96 and 40b1492.

📒 Files selected for processing (3)
  • CHANGES.md
  • packages/core/src/parser.test.ts
  • packages/core/src/parser.ts

Comment on lines 971 to 995
export function getDocPage(
parser: Parser<Mode, unknown, unknown>,
args: readonly string[] = [],
argsOrOptions?: readonly string[] | ParseOptions,
options?: ParseOptions,
): DocPage | undefined | Promise<DocPage | undefined> {
if (Array.isArray(argsOrOptions)) {
if (parser.$mode === "sync") {
return getDocPageSyncImpl(
parser as Parser<"sync", unknown, unknown>,
argsOrOptions,
options,
);
}
return getDocPageAsyncImpl(parser, argsOrOptions, options);
}
const resolved = (argsOrOptions as ParseOptions | undefined) ?? options;
if (parser.$mode === "sync") {
return getDocPageSyncImpl(
parser as Parser<"sync", unknown, unknown>,
args,
options,
[],
resolved,
);
}
return getDocPageAsyncImpl(parser, args, options);
return getDocPageAsyncImpl(parser, [], resolved);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Prefer dispatchByMode() over manual mode checks.

The implementation uses manual parser.$mode === "sync" branching. Per coding guidelines, mode-based type returns should use dispatchByMode() from mode-dispatch.ts instead of manual mode checks with type assertions.

♻️ Suggested refactor using dispatchByMode
 export function getDocPage(
   parser: Parser<Mode, unknown, unknown>,
   argsOrOptions?: readonly string[] | ParseOptions,
   options?: ParseOptions,
 ): DocPage | undefined | Promise<DocPage | undefined> {
   if (Array.isArray(argsOrOptions)) {
-    if (parser.$mode === "sync") {
-      return getDocPageSyncImpl(
-        parser as Parser<"sync", unknown, unknown>,
-        argsOrOptions,
-        options,
-      );
-    }
-    return getDocPageAsyncImpl(parser, argsOrOptions, options);
+    return dispatchByMode(
+      parser.$mode,
+      () =>
+        getDocPageSyncImpl(
+          parser as Parser<"sync", unknown, unknown>,
+          argsOrOptions,
+          options,
+        ),
+      () => getDocPageAsyncImpl(parser, argsOrOptions, options),
+    );
   }
   const resolved = (argsOrOptions as ParseOptions | undefined) ?? options;
-  if (parser.$mode === "sync") {
-    return getDocPageSyncImpl(
-      parser as Parser<"sync", unknown, unknown>,
-      [],
-      resolved,
-    );
-  }
-  return getDocPageAsyncImpl(parser, [], resolved);
+  return dispatchByMode(
+    parser.$mode,
+    () =>
+      getDocPageSyncImpl(
+        parser as Parser<"sync", unknown, unknown>,
+        [],
+        resolved,
+      ),
+    () => getDocPageAsyncImpl(parser, [], resolved),
+  );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/parser.ts` around lines 971 - 995, The function getDocPage
currently branches on parser.$mode and uses type assertions; replace that manual
branching with a call to dispatchByMode(parser, handlers) so the mode-dispatched
return types are handled for you: call dispatchByMode with a sync handler that
returns getDocPageSyncImpl(parser as Parser<"sync",...,>, args, options) and an
async handler that returns getDocPageAsyncImpl(parser, args, options); ensure
both the array-argument and no-argument paths delegate into dispatchByMode (pass
[] when argsOrOptions is not an array) and remove the explicit parser.$mode
checks and type-asserting branches.

Resolve args and options before the mode-based dispatch so each
function has a single mode branch instead of duplicating it across
the array/non-array paths.

#739 (comment)
#739 (comment)

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

dahlia commented Mar 26, 2026

@codex review

@dahlia
Copy link
Copy Markdown
Owner Author

dahlia commented Mar 26, 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 fixes an issue where getDocPage(), getDocPageSync(), and getDocPageAsync() would misinterpret a ParseOptions object as an args array when passed as the second argument. The functions have been updated to correctly accept ParseOptions directly as the second argument, simplifying the process of passing annotations without needing an explicit empty args array. New test cases were added to validate this behavior across various scenarios, and the CHANGES.md file has been updated to document this fix and API enhancement. No feedback to provide.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

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

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.

♻️ Duplicate comments (1)
packages/core/src/parser.ts (1)

888-895: ⚠️ Potential issue | 🟠 Major

Don't let sync parser exceptions escape getDocPageAsync().

Promise.resolve(getDocPageSyncImpl(...)) evaluates the sync path before the Promise exists, so exceptions from parse() or getDocFragments() still throw synchronously instead of rejecting. That breaks callers relying on .catch() from this always-Promise API.

♻️ Proposed fix
   if (parser.$mode === "sync") {
-    return Promise.resolve(
-      getDocPageSyncImpl(
-        parser as Parser<"sync", unknown, unknown>,
-        args,
-        opts,
-      ),
-    );
+    try {
+      return Promise.resolve(
+        getDocPageSyncImpl(
+          parser as Parser<"sync", unknown, unknown>,
+          args,
+          opts,
+        ),
+      );
+    } catch (error) {
+      return Promise.reject(error);
+    }
   }
In JavaScript, does `Promise.resolve(fn())` convert a synchronous exception thrown by `fn()` into a rejected promise, or does the exception escape before the promise is created?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/parser.ts` around lines 888 - 895, The sync branch
currently calls Promise.resolve(getDocPageSyncImpl(...)) which evaluates
getDocPageSyncImpl synchronously and lets exceptions escape; change it to defer
evaluation so sync exceptions become rejected promises—e.g., wrap the call in a
microtask (use Promise.resolve().then(() => getDocPageSyncImpl(...))) or catch
and return Promise.reject; update the branch that checks parser.$mode === "sync"
and replace the current Promise.resolve(getDocPageSyncImpl(...)) pattern so
getDocPageSyncImpl (the sync path) is invoked inside a .then callback (or
try/catch -> Promise.reject) to ensure errors are delivered as rejected promises
to callers of getDocPageAsync().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/core/src/parser.ts`:
- Around line 888-895: The sync branch currently calls
Promise.resolve(getDocPageSyncImpl(...)) which evaluates getDocPageSyncImpl
synchronously and lets exceptions escape; change it to defer evaluation so sync
exceptions become rejected promises—e.g., wrap the call in a microtask (use
Promise.resolve().then(() => getDocPageSyncImpl(...))) or catch and return
Promise.reject; update the branch that checks parser.$mode === "sync" and
replace the current Promise.resolve(getDocPageSyncImpl(...)) pattern so
getDocPageSyncImpl (the sync path) is invoked inside a .then callback (or
try/catch -> Promise.reject) to ensure errors are delivered as rejected promises
to callers of getDocPageAsync().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 202affc8-5d0b-48ba-bba3-31b5e5ba49f1

📥 Commits

Reviewing files that changed from the base of the PR and between 40b1492 and eb3760b.

📒 Files selected for processing (1)
  • packages/core/src/parser.ts

@dahlia dahlia merged commit 6382e76 into main Mar 26, 2026
8 checks passed
@dahlia dahlia deleted the fix/issue-480-getdocpage-options branch March 26, 2026 15:12
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.

The getDocPage() helper cannot accept annotations as the second argument without an explicit empty args array

1 participant