Skip to content

feat(oxfmt): Support gql-in-js substitution#19670

Merged
graphite-app[bot] merged 1 commit intomainfrom
02-24-feat_oxfmt_support_gql-in-js_substitution
Feb 26, 2026
Merged

feat(oxfmt): Support gql-in-js substitution#19670
graphite-app[bot] merged 1 commit intomainfrom
02-24-feat_oxfmt_support_gql-in-js_substitution

Conversation

@leaysgur
Copy link
Member

@leaysgur leaysgur commented Feb 24, 2026

Part of #15180

gql-in-js is the simplest among other xxx-in-js cases.

It works by:

  • splitting the template with ${}, converting each part into an IR using:
    • prettier({ parser:"graphql" }) and oxc_formatter respectively
  • and then interleaving them

However, for this to work, two things are essential:

  • Obtaining a formatted Doc from Prettier, rather than just a formatted string.
  • Converting the Doc JSON into oxc_formatter's IR.

The diffs primarily arise from the implementation of these two.

TODOs

  • Implement by Claude
  • Self review and fix up
  • Check bench, ecosystem

Copy link
Member Author

leaysgur commented Feb 24, 2026


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions github-actions bot added A-cli Area - CLI A-formatter Area - Formatter C-enhancement Category - New feature or request labels Feb 24, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 24, 2026

Merging this PR will not alter performance

✅ 48 untouched benchmarks
⏩ 7 skipped benchmarks1


Comparing 02-24-feat_oxfmt_support_gql-in-js_substitution (204128f) with main (06983a4)2

Open in CodSpeed

Footnotes

  1. 7 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (a447840) during the generation of this report, so 06983a4 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@leaysgur leaysgur force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch 6 times, most recently from 54ac372 to c46f385 Compare February 25, 2026 05:11
@leaysgur leaysgur changed the base branch from main to graphite-base/19670 February 25, 2026 05:40
@leaysgur leaysgur force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch from c46f385 to b898419 Compare February 25, 2026 05:40
@leaysgur leaysgur changed the base branch from graphite-base/19670 to 02-25-refactor_oxfmt_apply_rethrow_error_for_all_pool.run_calls February 25, 2026 05:40
@leaysgur leaysgur force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch from b898419 to bf5105e Compare February 25, 2026 06:29
@leaysgur leaysgur force-pushed the 02-25-refactor_oxfmt_apply_rethrow_error_for_all_pool.run_calls branch from 717dd35 to 1d19661 Compare February 25, 2026 06:29
@leaysgur leaysgur force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch 2 times, most recently from 94448c6 to b0ec516 Compare February 26, 2026 03:39
@leaysgur leaysgur force-pushed the 02-25-refactor_oxfmt_apply_rethrow_error_for_all_pool.run_calls branch from 1d19661 to 87abbe8 Compare February 26, 2026 03:39
@leaysgur leaysgur force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch from b0ec516 to 188eba2 Compare February 26, 2026 04:49
@leaysgur
Copy link
Member Author

leaysgur commented Feb 26, 2026

Oxfmt Ecosystem CI

suite oxfmt@latest 188eba2 branch
actualbudget/actual
huggingface/huggingface.js
vercel/turborepo
eggjs/egg
AmanVarshney01/create-better-t-stack
formatjs/formatjs
vuejs/pinia
vuejs/core
cloudflare/agents
fuma-nama/fumadocs
monkeytypegame/monkeytype
cnpm/cnpmcore
mastodon/mastodon
npmx-dev/npmx.dev
fastify/fastify-vite
rolldown/rolldown
lichess-org/lila
tale/headplane
Comfy-Org/ComfyUI_frontend
aidenybai/react-grab
getsentry/sentry-javascript
openclaw/openclaw
dyad-sh/dyad

@graphite-app graphite-app bot changed the base branch from 02-25-refactor_oxfmt_apply_rethrow_error_for_all_pool.run_calls to graphite-base/19670 February 26, 2026 05:19
@graphite-app graphite-app bot force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch from 188eba2 to ff5e58b Compare February 26, 2026 05:26
@graphite-app graphite-app bot force-pushed the graphite-base/19670 branch from 87abbe8 to a447840 Compare February 26, 2026 05:26
@leaysgur
Copy link
Member Author

leaysgur commented Feb 26, 2026

In the oxfmt-ecosystem-ci, it seemed only sentry-javascript was using gql (and it only had about 5 files...).

However, the fact that no differences were detected there means there's at least no issue there.


Benched with https://github.com/redwoodjs/graphql shows:

Benchmark 1: dist-main
  Time (mean ± σ):      1.978 s ±  0.050 s    [User: 13.350 s, System: 0.871 s]
  Range (min … max):    1.892 s …  2.038 s    10 runs

Benchmark 2: dist
  Time (mean ± σ):      2.085 s ±  0.034 s    [User: 14.097 s, System: 0.922 s]
  Range (min … max):    2.034 s …  2.143 s    10 runs

Summary
  dist-main ran
    1.05 ± 0.03 times faster than dist

Since it performs more operations than the previous code path, a performance decrease is expected.

The result doesn't seem to be too bad.

@graphite-app graphite-app bot changed the base branch from graphite-base/19670 to main February 26, 2026 05:26
@graphite-app graphite-app bot force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch from ff5e58b to afeb919 Compare February 26, 2026 05:27
@leaysgur leaysgur force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch from afeb919 to 204128f Compare February 26, 2026 05:33
@leaysgur leaysgur marked this pull request as ready for review February 26, 2026 06:45
@leaysgur leaysgur requested a review from Dunqing as a code owner February 26, 2026 06:45
Copilot AI review requested due to automatic review settings February 26, 2026 06:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements GraphQL-in-JS template literal formatting as part of the broader xxx-in-js support effort (#15180). It introduces a new Doc→IR conversion path that allows oxc_formatter to properly respect printWidth for embedded languages, addressing a key limitation of the previous string-based approach.

Changes:

  • Adds GraphQL template literal formatting via a new Doc→IR path that handles both no-substitution templates and templates with expressions
  • Implements bidirectional conversion between Prettier's Doc format and oxc_formatter's IR format
  • Refactors the embedded language formatting architecture with new format_embedded_doc callback and write_embedded_ir helper
  • Updates all NAPI bindings and JavaScript integration code to support the new callback

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/oxc_formatter/src/print/template/embed/graphql.rs New module implementing GraphQL template formatting with quasi analysis, comment handling, and expression interpolation
crates/oxc_formatter/src/print/template/embed/mod.rs Refactored embed module with new IR writing infrastructure and template character escaping
apps/oxfmt/src/prettier_compat/from_prettier_doc.rs New module converting Prettier Doc JSON to EmbeddedIR with comprehensive test coverage
apps/oxfmt/src/core/external_formatter.rs Extended with new format_embedded_doc callback supporting batch Doc formatting
apps/oxfmt/src-js/libs/apis.ts Added formatEmbeddedDoc function using Prettier's internal printToDoc API
apps/oxfmt/src-js/index.ts, cli.ts, cli-worker.ts, worker-proxy.ts Integration updates for new callback across all entry points
apps/oxfmt/src/main_napi.rs, api/*.rs NAPI binding updates for new callback signature
apps/oxfmt/cmp.js Comparison script for testing against Prettier's GraphQL formatting
crates/oxc_formatter/src/lib.rs, external_formatter.rs Public API updates exposing new EmbeddedIR types

Copy link
Member

@Dunqing Dunqing left a comment

Choose a reason for hiding this comment

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

Nice! You also solved the printWidth issue!

@Dunqing
Copy link
Member

Dunqing commented Feb 26, 2026

One thing I don't know is why we need EmbeddedIR rather than using FormatElement directly.

@graphite-app graphite-app bot added the 0-merge Merge with Graphite Merge Queue label Feb 26, 2026
@graphite-app
Copy link
Contributor

graphite-app bot commented Feb 26, 2026

Merge activity

Part of #15180

gql-in-js is the simplest among other xxx-in-js cases.

It works by:

- splitting the template with `${}`, converting each part into an IR using:
  - `prettier({ parser:"graphql" })` and `oxc_formatter` respectively
- and then interleaving them

However, for this to work, two things are essential:

- Obtaining a formatted `Doc` from Prettier, rather than just a formatted `string`.
- Converting the `Doc` JSON into `oxc_formatter`'s IR.

The diffs primarily arise from the implementation of these two.

### TODOs

- [x] Implement by Claude
- [x] Self review and fix up
- [x] Check bench, ecosystem
@graphite-app graphite-app bot force-pushed the 02-24-feat_oxfmt_support_gql-in-js_substitution branch from 204128f to ca68ea6 Compare February 26, 2026 15:24
@graphite-app graphite-app bot merged commit ca68ea6 into main Feb 26, 2026
21 checks passed
@graphite-app graphite-app bot deleted the 02-24-feat_oxfmt_support_gql-in-js_substitution branch February 26, 2026 15:30
@graphite-app graphite-app bot removed the 0-merge Merge with Graphite Merge Queue label Feb 26, 2026
@leaysgur
Copy link
Member Author

One thing I don't know is why we need EmbeddedIR rather than using FormatElement directly.

I believe it was because the lifetimes FormatElement<'a> and GroupId allocation were confined to oxc_formatter, and I couldn't figure out how to simply bring them over to the oxfmt side.

However, I feel like I can do it now, so I'll try it as a refactoring. 👌🏻

graphite-app bot pushed a commit that referenced this pull request Feb 27, 2026
…` directly (#19800)

Follow up on #19670 (comment)

It seems it was achievable without requiring such major changes.
Thank you for your suggestion! 🙏🏻
camc314 pushed a commit that referenced this pull request Mar 2, 2026
# Oxlint
### 🚀 Features

- 2e0e1d0 linter/no-unused-vars: Add experimental fix mode controls
(off|suggestion|fix) (#19774) (camc314)
- f34f6fa linter: Introduce typeCheck config option (#19764) (camc314)
- 694be7d linter: Introduce typeAware as config options (#19614)
(camc314)
- 655c38f semantic: Add "did you mean?" suggestions to undefined name
errors (#19102) (copilot-swe-agent)
- e97a57e linter/id-length: Use serde to deserialize rule options
(#19636) (camc314)
- c4a3677 parser: Report error for initializer in ambient context
(#19187) (camc314)
- 346045a linter/id-length: Add `checkGeneric` option (#19634) (camc314)

### 🐛 Bug Fixes

- 1b7a937 linter: Correct double-comparisons fix with swapped operands
(#19846) (camc314)
- c308857 linter/consistent_type_imports: Add missing help and notes to
diagnostics (#19827) (Daniel Osmond)
- 7682e5a linter/plugins: Decode escapes in identifier tokens (#19838)
(overlookmotel)
- f368fcd linter/consistent_type_assertions: Add missing with_help and
with_note to diagnostics (#19826) (Daniel Osmond)
- 04e6223 npm: Add `preferUnplugged` for Yarn PnP compatibility (#19829)
(Boshen)
- 86d5037 linter: Add help text to no-extend-native,
no-useless-backreference (#19733) (Anthony Amaro)
- 50e8eff linter: Add .with_help() to operator-assignment,
no-nonoctal-decimal-escape (#19732) (Anthony Amaro)
- 1417bdc linter/no-wrapper-object-types: Add help messages to missing
diagnostics (#19771) (Daniel Osmond)
- 0838477 linter/ban_ts_comment: Add help and notes to missing
diagnostics (#19781) (Daniel Osmond)
- e8c77cf linter/adjacent_overload_signatures: Add missing diagnostics
(#19780) (Daniel Osmond)
- 28834ac linter/ban_types: Add missing help and note to diagnostics
(#19782) (Daniel Osmond)
- fd938d3 linter/prefer-enum-initializers: Add help messages to missing
diagnostics (#19772) (Daniel Osmond)
- eb928ee linter/no-dynamic-delete: Add help messages to missing
diagnostics (#19768) (Daniel Osmond)
- a985666 linter/no-empty-interface: Add help messages to missing
diagnostics (#19769) (Daniel Osmond)
- 2dc0ceb linter/no-extra-non-null-assertion: Add help messages to
missing diagnostics (#19770) (Daniel Osmond)
- 95d5d66 linter/no-dupe-keys: Handle `__proto__` proto setters in
(#19762) (camc314)
- 24ff0db linter/exhaustive-deps: False positive for member expressions
in IIFEs (#19751) (Dennis Chen)
- 7243a58 linter/no-use-before-define: Honor `ignoreTypeReferences` when
value and type name collisions (#19747) (Dimava)
- eefd818 linter/explicit-module-boundary-types: Add help messages to
missing diagnostics (#19736) (Daniel Osmond)
- 0440e9a linter: Add help text to no_control_regex, no_fallthrough,
no_param_reassign (#19655) (Anthony Amaro)
- e84cb2f react/display-name: Handle merged type+value context symbols
(#19608) (camc314)
- ce7e253 linter/prefer-object-from-entries: Require exact path match in
unicorn helper (#19687) (camc314)
- f5694ce estree/tokens: Reverse field order of `regex` object in tokens
(#19679) (overlookmotel)
- b2b7a55 estree/tokens: Generate tokens for files with BOM (#19535)
(overlookmotel)
- 0722721 linter/jsx-curly-brace-presence: False positive with prop &
mixed quotes (#19674) (camc314)
- 3496acd linter: Enhance diagnostic help messages for eslint rules
(#19653) (Anthony Amaro)
- e384e94 linter: Enhance help diagnostic messages for more eslint rules
(#19658) (Anthony Amaro)
- a4d5b34 linter: Avoid non-promise catch false positives (#19574)
(camc314)
- 5706f38 linter: `unicorn/no-array-callback-reference` skip `Effect.*`
array-like methods name. (#19633) (Said Atrahouch)

### ⚡ Performance

- 05ccf9f linter/plugins: Transfer tokens via raw transfer (#19893)
(overlookmotel)
- 4b0611a estree/tokens: Introduce `ESTreeTokenConfig` trait (#19842)
(overlookmotel)
- ec88f6a estree/tokens: Serialize tokens while visiting AST (#19726)
(overlookmotel)
- d4dcf26 linter/plugins: Remove `typescript` from bundle (#19531)
(overlookmotel)
- 6a6513c linter/plugins: Use Oxc tokens in plugins (#19498) (camc314)

### 📚 Documentation

- d86f59e linter: Improve docs for no-useless-concat, mark as pending
fixer. (#19859) (connorshea)
- caa091d linter/plugins: Correct doc comments for `initTokens` (#19530)
(overlookmotel)
- 2fa936f README.md: Map npm package links to npmx.dev (#19666) (Boshen)
- dc0ff73 linter/no-useless-constructor: Warn for parameter properties
as well (#19638) (Ole Asteo)
# Oxfmt
### 🚀 Features

- 5141bc2 formatter: Support trailing ignore comments (#19304) (Andreas
Lubbe)
- 4888a99 oxfmt/lsp: Support other schemes beside `file://` and
`untitled://` (#19872) (Sysix)
- 14a0181 oxfmt: Support `graphql()` variant for gql-in-js (#19703)
(leaysgur)
- ca68ea6 oxfmt: Support gql-in-js substitution (#19670) (leaysgur)
- 035933c formatter,oxfmt: Support js-in-vue (partially) (#19514)
(leaysgur)
- 9e11dc6 parser,estree,coverage: Collect tokens in parser and convert
to ESTree format (#19497) (camc314)

### 🐛 Bug Fixes

- 8e3842d oxfmt: Avoid embedded TSFN crash by returning errors as data
(take2) (#19806) (Yuji Sugiura)
- 04e6223 npm: Add `preferUnplugged` for Yarn PnP compatibility (#19829)
(Boshen)
- e540585 oxfmt: Support tailwind sort for CSS/LESS/SCSS (#19803)
(leaysgur)
- 93bb861 formatter: Trim trailing whitespace before breaking line
(#19740) (leaysgur)
- b85f97b formatter: Drop blank line between terminal call and first
chain member (#19659) (Dunqing)

### ⚡ Performance

- b3b2d30 parser: Introduce `ParserConfig` (#19637) (overlookmotel)

### 📚 Documentation

- 2fa936f README.md: Map npm package links to npmx.dev (#19666) (Boshen)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-formatter Area - Formatter C-enhancement Category - New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants