CLI: parser rejects the TypeScript satisfies operator, silently dropping every component in the file
Summary
The bundled TypeScript parser does not support the satisfies operator (TypeScript 4.9, released November 2022). Any module using it fails to parse, and every component defined in that module is silently omitted from the analysis — no warning, no non-zero exit code. The run reports success and the dashboard simply shows fewer components than the repo contains.
Usage data is lost too, not just the definitions: a <Broken /> usage inside a module that does parse is dropped from the parsed module's dependency list, because the target component never got registered. So components that survive the parser still get under-counted usages whenever a consumer happens to use satisfies.
In our monorepo this affects 1100 of 11,258 modules (~10%), 186 of them non-story/non-spec source files.
Steps to Reproduce
mkdir omlet-satisfies-repro && cd omlet-satisfies-repro && git init
npm pkg set name=omlet-satisfies-repro
src/Ok.tsx — no satisfies:
const COLORS = {red: '#f00'} as const;
export function Ok() {
return <span style={{color: COLORS.red}}>ok</span>;
}
src/Broken.tsx — as const satisfies:
const COLORS = {red: '#f00'} as const satisfies Record<string, string>;
export function Broken() {
return <span style={{color: COLORS.red}}>broken</span>;
}
src/Broken2.tsx — bare satisfies:
const SIZES = {s: 1} satisfies Record<string, number>;
export function Broken2() {
return <span>{SIZES.s}</span>;
}
src/App.tsx — consumes all three:
import {Broken} from './Broken';
import {Broken2} from './Broken2';
import {Ok} from './Ok';
export function App() {
return (
<div>
<Ok />
<Broken />
<Broken2 />
</div>
);
}
Then:
npx @omlet/cli analyze --dry-run
Observed Behavior
The CLI reports success:
✔ Detecting components and collecting component usages…
Summary:
2 modules have been scanned successfully and 2 components detected.
4 modules exist; 2 were scanned. Nothing in the console names the 2 that were not, and the exit code is 0.
omlet.out.json has the detected components —
— and the reason, only in parser_errors:
[
{"name": "TSParseError", "reason": ["Expected a semicolon at line 1:38"], "file": ".../src/Broken.tsx"},
{"name": "TSParseError", "reason": ["Expected a semicolon at line 1:21"], "file": ".../src/Broken2.tsx"}
]
Both columns point exactly at the satisfies keyword. Broken and Broken2 are absent from components, and App's dependencies array contains a single edge, to Ok — the <Broken /> and <Broken2 /> usages are gone.
Two error shapes show up depending on the syntax, neither of which mentions satisfies in the as const case:
| Syntax |
Reported error |
} as const satisfies Record<string, T>; |
Expected a semicolon at line N:C |
} satisfies T; after an object literal |
Expected a semicolon at line N:C |
[...acc, x satisfies T] — expression position in a list |
Expected ',', got 'satisfies' |
} as const satisfies { … } — inline object type |
Expected ';', got 'satisfies' |
Expected Behavior
satisfies parses, and the components in those modules are detected.
Failing that, two things would have made this diagnosable in minutes rather than by diffing --dry-run output:
- Do not report a run as successful when modules failed to parse. The summary line prints the count of modules scanned successfully but never the count that failed. Printing
N modules failed to parse (see parser_errors) — or a --fail-on-parse-error flag for CI — would surface this immediately.
- Name
satisfies in the error. Expected a semicolon next to as const satisfies is a misleading message for an unsupported-syntax condition.
Impact at scale
From one analyze --dry-run over our monorepo (11,258 modules, 3,990 components detected):
- 1,111 modules failed to parse; 1,100 of them (99%) are attributable to
satisfies. The remainder are generic arrow components (Unexpected token X. Expected jsx identifier) and a couple of other unsupported-syntax cases.
- 186 of the affected files are non-story, non-spec source.
- Of 3,135
Expected a semicolon occurrences, 3,132 land on a line containing satisfies.
- Removing a single
satisfies from one design-system component (} as const satisfies Record<string, TagColorScheme>;) and re-running took the component count from 3,990 to 3,991 and parse errors from 1,111 to 1,110 — the component appeared in the analysis with no other change.
The practical effect is that a widely used component can be invisible in the dashboard while its siblings in the same directory are present, which reads as a detection bug rather than a parser one. Because satisfies is also idiomatic in Storybook's satisfies Meta<typeof X> / satisfies Story pattern, the story-to-component linkage is broken across essentially every CSF3 codebase written since TS 4.9.
Environment
- Component: CLI (
@omlet/cli)
- Omlet CLI version: 2.2.0 (latest published)
- Node.js: v24.16.0
- OS: macOS 26.6 (arm64)
- TypeScript: 5.x,
tsconfigPath set, moduleResolution: bundler
CLI: parser rejects the TypeScript
satisfiesoperator, silently dropping every component in the fileSummary
The bundled TypeScript parser does not support the
satisfiesoperator (TypeScript 4.9, released November 2022). Any module using it fails to parse, and every component defined in that module is silently omitted from the analysis — no warning, no non-zero exit code. The run reports success and the dashboard simply shows fewer components than the repo contains.Usage data is lost too, not just the definitions: a
<Broken />usage inside a module that does parse is dropped from the parsed module's dependency list, because the target component never got registered. So components that survive the parser still get under-counted usages whenever a consumer happens to usesatisfies.In our monorepo this affects 1100 of 11,258 modules (~10%), 186 of them non-story/non-spec source files.
Steps to Reproduce
src/Ok.tsx— nosatisfies:src/Broken.tsx—as const satisfies:src/Broken2.tsx— baresatisfies:src/App.tsx— consumes all three:Then:
Observed Behavior
The CLI reports success:
4 modules exist; 2 were scanned. Nothing in the console names the 2 that were not, and the exit code is 0.
omlet.out.jsonhas the detected components —— and the reason, only in
parser_errors:[ {"name": "TSParseError", "reason": ["Expected a semicolon at line 1:38"], "file": ".../src/Broken.tsx"}, {"name": "TSParseError", "reason": ["Expected a semicolon at line 1:21"], "file": ".../src/Broken2.tsx"} ]Both columns point exactly at the
satisfieskeyword.BrokenandBroken2are absent fromcomponents, andApp'sdependenciesarray contains a single edge, toOk— the<Broken />and<Broken2 />usages are gone.Two error shapes show up depending on the syntax, neither of which mentions
satisfiesin theas constcase:} as const satisfies Record<string, T>;Expected a semicolon at line N:C} satisfies T;after an object literalExpected a semicolon at line N:C[...acc, x satisfies T]— expression position in a listExpected ',', got 'satisfies'} as const satisfies { … }— inline object typeExpected ';', got 'satisfies'Expected Behavior
satisfiesparses, and the components in those modules are detected.Failing that, two things would have made this diagnosable in minutes rather than by diffing
--dry-runoutput:N modules failed to parse (see parser_errors)— or a--fail-on-parse-errorflag for CI — would surface this immediately.satisfiesin the error.Expected a semicolonnext toas const satisfiesis a misleading message for an unsupported-syntax condition.Impact at scale
From one
analyze --dry-runover our monorepo (11,258 modules, 3,990 components detected):satisfies. The remainder are generic arrow components (Unexpected token X. Expected jsx identifier) and a couple of other unsupported-syntax cases.Expected a semicolonoccurrences, 3,132 land on a line containingsatisfies.satisfiesfrom one design-system component (} as const satisfies Record<string, TagColorScheme>;) and re-running took the component count from 3,990 to 3,991 and parse errors from 1,111 to 1,110 — the component appeared in the analysis with no other change.The practical effect is that a widely used component can be invisible in the dashboard while its siblings in the same directory are present, which reads as a detection bug rather than a parser one. Because
satisfiesis also idiomatic in Storybook'ssatisfies Meta<typeof X>/satisfies Storypattern, the story-to-component linkage is broken across essentially every CSF3 codebase written since TS 4.9.Environment
@omlet/cli)tsconfigPathset,moduleResolution: bundler