Skip to content

[types] Hand-maintain StyleXCSSTypes.d.ts for autocompletable unions - #1877

Closed
henryqdineen wants to merge 1 commit into
facebook:mainfrom
henryqdineen:hqd-stylex-css-types-dts
Closed

henryqdineen wants to merge 1 commit into
facebook:mainfrom
henryqdineen:hqd-stylex-css-types-dts

Conversation

@henryqdineen

Copy link
Copy Markdown
Collaborator

What changed / motivation ?

This answers the question I asked in #1466: yes, maintaining a separate TypeScript definition of the CSS types is worth it, because the thing it enables cannot be expressed in Flow at all.

gen-types translates the Flow types into TypeScript, and that translation throws away the most useful thing these types can do, because a Flow union containing string collapses to string and discards every literal member:

Flow:       type Display = 'block' | 'flex' | string;
TypeScript: type Display = 'block' | 'flex' | (string & {});

string & {} is mutually assignable with string, so any string is still accepted, but the literals survive and editors keep suggesting them. (The trick explained.)

gen-types copies a .d.ts verbatim when one sits beside the Flow source, so checking this file in is enough to take over that translation. No build changes are needed.

100 of the 522 properties were typed as a bare string or number | string, so they offered no completions at all; roughly 340 more lost their literals to a string member. Those now autocomplete.

Linked PR/Issues

Answers the question raised in #1466.

Additional Context

The diff is deliberately minimal. This file is byte-identical to what gen-types already produced, once the 249 string(string & {}) substitutions are reversed. There are no type changes to audit: no properties added or removed, no keywords added or removed, nothing widened, nothing narrowed. Every string in the file is a plain union member or type-alias body — no index signatures, no Record<string, …>, no template literals, no generic arguments — so the substitution is mechanical.

Not a breaking change, and not a behavioural one. The unions that now end in (string & {}) previously ended in a bare string, which already accepted any string. Nothing new is accepted and nothing that typechecked before stops; the only change is that the literals are no longer erased.

packages/typescript-tests/src/open-unions.ts covers the behaviour: arbitrary strings and var() are still accepted, null still unsets a property, the CSS-wide keywords still resolve, literals survive, and closed unions stay closed.

One trap if you extend those tests: do not use NonNullable to inspect these unions. It is T & {} in TypeScript >= 4.9, and that intersection flattens them back down to string — so a test written with it will pass whether or not the literals actually survived, which is exactly the thing being tested. My first version of these tests had that bug.

The two files must now be kept in sync by hand. The next PR in this stack adds tests that enforce it — and writing those turned up a fair amount in the existing types that is incomplete, inconsistent, or simply wrong.

Pre-flight checklist

🤖 Generated with Claude Code

`gen-types` translates the Flow types into TypeScript, which loses the most
useful thing these types can do, because a Flow union containing `string`
collapses to `string` and discards every literal member:

    Flow:       type Display = 'block' | 'flex' | string;
    TypeScript: type Display = 'block' | 'flex' | (string & {});

`string & {}` is mutually assignable with `string`, so any string is still
accepted, but the literals survive and editors keep suggesting them. There is
no way to express this in Flow.

`gen-types` copies a `.d.ts` verbatim when one sits beside the Flow source, so
checking this file in is enough to take over that translation -- no build
changes. Verified byte-identical to what `gen-types` produced, once the 249
`string` -> `(string & {})` substitutions are reversed.

100 of the 522 properties were typed as bare `string` or `number | string` and
so offered no completions at all; another ~340 lost their literals to a
`string` member. Those now autocomplete.

`packages/typescript-tests/src/open-unions.ts` covers the behaviour: arbitrary
strings and `var()` are still accepted, `null` still unsets a property, the
CSS-wide keywords still resolve, literals survive, and closed unions stay
closed. Note `NonNullable` cannot be used to inspect these unions -- it is
`T & {}` in TypeScript >=4.9, and that intersection flattens them.

The two files must now be kept in sync by hand. Adding a css-tree-based test to
enforce that, and to check the literals against the CSS grammar, is the
intended follow-up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 11, 2026
@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

@henryqdineen is attempting to deploy a commit to the Meta Open Source Team on Vercel.

A member of the Team first needs to authorize it.

@henryqdineen

Copy link
Copy Markdown
Collaborator Author

Superseded by #1882. Reopened from branches in this repository rather than my fork, so the five PRs can be a proper stack — cross-fork stacks aren't supported, and each PR here was showing the earlier ones' commits in its diff. Same commits, unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant