Skip to content

feat: detect more tagged template patterns for external formatter (#658)#792

Open
todor-a wants to merge 3 commits into
dprint:mainfrom
todor-a:extend-tagged-tpl-detection
Open

feat: detect more tagged template patterns for external formatter (#658)#792
todor-a wants to merge 3 commits into
dprint:mainfrom
todor-a:extend-tagged-tpl-detection

Conversation

@todor-a
Copy link
Copy Markdown
Contributor

@todor-a todor-a commented May 28, 2026

Refs #658, #9, #643.


Transparency note: This PR was prepared with AI assistance (Claude).


The external-formatter infrastructure for tagged templates is in place — what was missing is detection for the embedded-language shapes prettier and oxfmt route automatically.

Coverage matrix

pattern language status
css...`` css existing
styled.foo... / `styled["x"]`... / styled(X)...`` css existing
styled.foo.attrs({})... / `styled(X).attrs({})`... css this PR
UpperIdent.extend(.attrs({}))?...`` css this PR
css.global... / `css.X`... css this PR
<X css={...} /> css this PR
<style jsx>{...}</style> (incl. global) css this PR
@Component({ styles: ... }) / { styles: [...] } css this PR
@Component({ template: ... }) html this PR
html... / `sql`... html / sql existing
graphql... / `gql`... (aliased) graphql this PR
graphql.experimental...`` graphql this PR
graphql(...) / graphql(x, ...) graphql this PR
markdown... / `md`... passthrough existing

How

  • normalize_member_tag (recursive) handles the tagged-template shapes (styled / extend / graphql / css member chains).
  • New detect_embedded_lang_for_tpl runs from gen_tpl for non-tagged shapes (call-arg, JSX, Angular) and walks the parent chain.
  • Substitution + re-indent backbone extracted into shared maybe_gen_tpl_with_external_formatter(tpl, lang, ctx).

Negative guards (each spec-pinned): foo.extend (lowercase), gql(...), someFunction(...), spread args, JSX attr ≠ css, <style> without jsx attr, non-Component callee, computed Angular keys.

Tests

tests/specs/external_formatter/{css,html,graphql}.txt + a tiny graphql stub in tests/spec_test.rs (idempotent under format-twice). Patterns mirrored from prettier src/language-js/embed/{css,graphql,utilities}.js and oxfmt apps/oxfmt/test/api/embedded_languages.test.ts.

Addresses

Out of scope

  • src/wasm_plugin.rs:69 still passes external_formatter: None. Wiring through the Wasm boundary is a host-plugin protocol change — separate PR. Until that lands, only Rust-API consumers see embedded formatting.
  • Bare styled...`` (oxfmt accepts, prettier doesn't).
  • Language-comment opt-in (/* GraphQL */ etc.).

cargo test --release clean.

todor-a added 3 commits May 28, 2026 14:54
…rint#658)

Extend `normalize_embedded_language_type` so the embedded formatter is
invoked for additional tag shapes covered by prettier:

- `styled.foo.attrs({})` (call on `styled.X` member)         -> css
- `styled(Component).attrs({})` (call on `styled(X)` call)    -> css
- `Component.extend` (uppercase ident `.extend` member)      -> css
- `Component.extend.attrs({})` (call on the above)            -> css
- `graphql.experimental` member                              -> graphql
- `gql` ident is aliased to `graphql` so external formatters
  only have to register one language key

Refactored the detection into a recursive helper so the call-on-member
and call-on-call shapes share the same logic.

Specs:
- new `tests/specs/external_formatter/graphql.txt` exercises `gql`,
  `graphql`, and `graphql.experimental` via a small whitespace-collapsing
  stub formatter registered in `tests/spec_test.rs`.
- new cases in `tests/specs/external_formatter/css.txt` cover the
  styled-components attrs/extend variants and verify that a lowercase
  `.extend` (`foo.extend`) is NOT treated as css.

Patterns verified against the prettier corpus in
`tests/format/js/multiparser-css/styled-components.js` and
`tests/format/js/multiparser-graphql/{graphql-tag,react-relay}.js`.
Extends the embedded-language detection (covered by dprint#658) further, with
patterns mirrored from oxfmt's embedded_languages.test.ts and prettier's
embed/graphql.js:

- `css.global` and any `css.<member>` -> css  (emotion-style)
- `graphql(`...`)` and `graphql(x, `...`)` call arguments -> graphql

The graphql call-argument case is handled by a new
`detect_embedded_lang_for_call_arg_tpl` that runs from `gen_tpl` and
walks up through the `ExprOrSpread` wrapper to the parent `CallExpr`.
Spread arguments are deliberately excluded; `gql(...)` and other
callees do not trigger the path (matching prettier).

The substitution / re-indentation backbone was extracted out of
`maybe_gen_tagged_tpl_with_external_formatter` into a shared
`maybe_gen_tpl_with_external_formatter(tpl, lang, ctx)` so both paths
share it.

Specs:
- new css cases for `css.global`...`` and `styled["a"]`...`` (computed
  member already worked, now pinned in tests).
- new graphql cases: `graphql(`...`)`, `graphql(schema, `...`)`, plus
  negatives `gql(`...`)`, `someFunction(`...`)`, `graphql(...args)`.

Patterns cross-referenced against oxc's
apps/oxfmt/test/api/embedded_languages.test.ts and prettier's
src/language-js/embed/{css,graphql}.js.
Extends embedded-language detection from bare template literals via parent
chain walks (mirroring prettier embed/css.js + embed/utilities.js):

- `<X css={`...`} />` JSX attribute -> css
- `<style jsx>{`...`}</style>` and `<style jsx global>` styled-jsx -> css
- `@Component({ template: `...` })` Angular decorator -> html
- `@Component({ styles: `...` })` and `{ styles: [`...`] }` -> css

A single `detect_embedded_lang_for_tpl` dispatches to per-pattern probes:
`detect_embedded_lang_for_call_arg_tpl`,
`detect_embedded_lang_for_jsx_tpl`,
`detect_embedded_lang_for_angular_component_tpl`.

Each guard checks the full parent chain — JSX attribute name must be `css`,
styled-jsx must have a `jsx` attribute on the `<style>` opening tag, Angular
must be inside a `Component(...)` call argument, computed property keys
(`[styles]`) are deliberately excluded.

Spec additions:
- css: JSX css prop, styled-jsx (with and without `global`), `<style>`
  without `jsx` attribute (negative), Angular `styles` (object and array
  forms), non-Component callee (negative), computed key (negative)
- html: Angular `template`, non-Component callee (negative), computed key
  (negative)

Patterns cross-referenced against:
- prettier src/language-js/embed/{css,utilities}.js
- oxc apps/oxfmt/test/api/embedded_languages.test.ts
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.

1 participant