feat: detect more tagged template patterns for external formatter (#658)#792
Open
todor-a wants to merge 3 commits into
Open
feat: detect more tagged template patterns for external formatter (#658)#792todor-a wants to merge 3 commits into
todor-a wants to merge 3 commits into
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
css...``styled.foo.../ `styled["x"]`.../styled(X)...``styled.foo.attrs({}).../ `styled(X).attrs({})`...UpperIdent.extend(.attrs({}))?...``css.global.../ `css.X`...<X css={...} /><style jsx>{...}</style>(incl.global)@Component({ styles:...})/{ styles: [...] }@Component({ template:...})html.../ `sql`...graphql.../ `gql`...(aliased)graphql.experimental...``graphql(...)/graphql(x,...)markdown.../ `md`...How
normalize_member_tag(recursive) handles the tagged-template shapes (styled / extend / graphql / css member chains).detect_embedded_lang_for_tplruns fromgen_tplfor non-tagged shapes (call-arg, JSX, Angular) and walks the parent chain.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>withoutjsxattr, non-Componentcallee, computed Angular keys.Tests
tests/specs/external_formatter/{css,html,graphql}.txt+ a tiny graphql stub intests/spec_test.rs(idempotent under format-twice). Patterns mirrored from prettiersrc/language-js/embed/{css,graphql,utilities}.jsand oxfmtapps/oxfmt/test/api/embedded_languages.test.ts.Addresses
html...`` formatting) — already worked via existingIdentpass-through; verified against the issue's repro.styled.div...`` already worked; this PR addsComponent.extend, `.attrs({})` variants.Out of scope
src/wasm_plugin.rs:69still passesexternal_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.styled...`` (oxfmt accepts, prettier doesn't)./* GraphQL */etc.).cargo test --releaseclean.