# AGENTS.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Overview This package is a **type checker for React Native's JS/Native boundary**. It detects backwards-incompatible changes between JavaScript and Native code to prevent crashes, particularly useful for: - Local development (detecting when native rebuild is needed) - Over-the-air (OTA) updates - Server Components with React Native The tool operates on JSON schema files generated by `@react-native/codegen`, making it agnostic to TypeScript/Flow. ## Architecture: Three-Stage Pipeline The compatibility check flows through three distinct stages: ``` Schema (new) ──┐ ├──▶ TypeDiffing ──▶ VersionDiffing ──▶ ErrorFormatting ──▶ Output Schema (old) ──┘ ``` ### Stage 1: TypeDiffing (`TypeDiffing.js`) **Pure type comparison** - Compares two type annotations and returns all structural differences. - Reports ALL differences between types (added/removed properties, union changes, etc.) - Returns `ComparisonResult` with status: `matching`, `skipped`, `properties`, `members`, `unionMembers`, `functionChange`, `positionalTypeChange`, `nullableChange`, or `error` - **Must remain pure** - no React Native-specific logic belongs here ### Stage 2: VersionDiffing (`VersionDiffing.js`) **Semantic safety analysis** - Interprets TypeDiffing results in the context of React Native's boundary. - Determines if changes are safe based on **data flow direction**: - `toNative`: Data flows from JS to Native (method parameters, component props) - `fromNative`: Data flows from Native to JS (return values, getConstants) - `both`: Bidirectional flow - Encodes compatibility rules: - Adding to a union sent TO native = **UNSAFE** (native won't expect it) - Removing from a union received FROM native = **UNSAFE** (JS won't handle it) - Adding optional properties = **SAFE** - Making required properties optional when sending TO native = **UNSAFE** ### Stage 3: ErrorFormatting (`ErrorFormatting.js`) **Human-readable output** - Converts deep error objects into formatted strings. - **Must remain pure** - no business logic ### Supporting Files - **`ComparisonResult.js`**: Type definitions for all comparison result shapes - **`DiffResults.js`**: Type definitions for schema diff results, error codes, and summary types - **`SortTypeAnnotations.js`**: Sorting utilities for comparing type annotations in a stable order - **`convertPropToBasicTypes.js`**: Converts Component prop types to standard type annotations for comparison - **`index.js`**: Public API - exports `compareSchemas()` returning a `CompatCheckResult` ## Key Type Definitions ```javascript // Main comparison statuses type ComparisonResult = | {status: 'matching'} // Types are identical | {status: 'skipped'} // No old type to compare | {status: 'properties', ...} // Object property changes | {status: 'members', ...} // Enum member changes | {status: 'unionMembers', ...} // Union member changes | {status: 'functionChange', ...}// Function signature changes | {status: 'error', ...} // Incompatible type change // Summary statuses type DiffSummary = { status: 'ok' | 'patchable' | 'incompatible', incompatibilityReport: {...} } ``` ## Commands Run tests from the react-native-compatibility-check directory: ```bash cd packages/react-native-compatibility-check # Run all tests yarn test # Run a specific test file yarn test src/__tests__/TypeDiffing-test.js # Run tests matching a pattern yarn test --testNamePattern="compareTypes on unions" ``` **Meta employees**: Use `js1 test SUBPATH` instead (e.g., `js1 test react-native-compatibility-check`). ## Testing Patterns ### Test Fixtures Tests use Flow files in `__tests__/__fixtures__/` parsed by `@react-native/codegen`: - **Native Modules**: `native-module-*/NativeModule.js.flow` - **Native Components**: `native-component-*/NativeComponent.js.flow` The `getTestSchema()` utility parses these fixtures into schema objects. ### Test Structure - **TypeDiffing-test.js**: Tests pure type comparison logic - **VersionDiffing-test.js**: Tests safety analysis with boundary direction - **ErrorFormatting-test.js**: Tests error message generation (uses snapshots) ### Adding Test Cases 1. Create a new fixture directory under `__tests__/__fixtures__/` 2. Add a `.js.flow` file defining a Native Module or Component 3. Load it in tests using `getTestSchema(__dirname, '__fixtures__', 'fixture-name', 'FileName.js.flow')` ## Design Principles ### Separation of Concerns - **TypeDiffing**: Pure type comparison. Should work for ANY JavaScript types. - **VersionDiffing**: React Native boundary semantics. Only place for RN-specific logic. - **ErrorFormatting**: Presentation only. No business logic. ### Module-scope Type Registries `TypeDiffing.js` uses module-scope variables (`_newerTypesReg`, `_olderTypesReg`, `_newerEnumMap`, `_olderEnumMap`) to avoid threading lookups through all recursive calls. This is acceptable because the logic is serial. ### Structural Type Comparison Types are compared structurally, not nominally. Two different type aliases with identical structure are considered matching. ## Compatibility Rules Reference ### Data Flowing TO Native (parameters, props) | Change | Safe? | |--------|-------| | Add optional property | ✅ | | Add required property | ❌ | | Remove property | ✅ | | Make property optional | ❌ | | Add union member | ❌ | | Remove union member | ✅ | | Add enum member | ❌ | | Remove enum member | ✅ | ### Data Flowing FROM Native (return values, constants) | Change | Safe? | |--------|-------| | Add optional property | ✅ | | Add required property | ❌ | | Remove property | ✅ | | Make property required | ❌ | | Add union member | ✅ | | Remove union member | ❌ | | Add enum member | ✅ | | Remove enum member | ❌ | ## Common Gotchas 1. **Component Commands**: Adding/removing commands is intentionally allowed even though it could cause OTA issues, because there's no feature detection mechanism for commands. 2. **Union ordering**: Unions are sorted before comparison, so `'a' | 'b'` equals `'b' | 'a'`. 3. **Nullable vs Optional**: These are distinct concepts: - Optional: Property may be absent (`prop?: T`) - Nullable: Value may be null/undefined (`prop: ?T`) 4. **Type Aliases**: Resolved during comparison. Different alias names with identical structure are treated as matching. 5. **Component Props with Defaults**: `WithDefault` types are stripped during comparison - only the underlying type matters for compatibility. 6. **Int32EnumTypeAnnotation**: Currently converted to `AnyTypeAnnotation` because the tool lacks support for number literal unions. ## Adding New Type Support 1. Add the type case to `compareTypeAnnotation()` in `TypeDiffing.js` 2. Add sorting logic in `SortTypeAnnotations.js` (`compareTypeAnnotationForSorting`) 3. Add formatting in `ErrorFormatting.js` (`formatTypeAnnotation`) 4. Add test fixtures and tests covering the new type 5. If it affects safety analysis, update `VersionDiffing.js` checks ## Code Style - All source files use `@flow strict-local` or `@flow strict` - All source files require `@format` pragma for Prettier - Tests use `@noflow` or `@flow` (not strict)