Skip to content

CLI: parser rejects the TypeScript satisfies operator, silently dropping every component in the file #11

Description

@Manuel-Manoury

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.tsxas 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 —

["App", "Ok"]

— 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:

  1. 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.
  2. 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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions