mirror of
https://github.com/react/react-native.git
synced 2026-09-28 13:23:09 +08:00
Replace CONTRIBUTING.md with AGENTS.md (#55301)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/55301 Replace the brief CONTRIBUTING.md with a comprehensive AGENTS.md that provides AI coding assistants with detailed guidance for working with the react-native-compatibility-check package. The new documentation covers the three-stage pipeline architecture, compatibility rules, testing patterns, and design principles that were previously only partially documented. Changelog: [Internal] Reviewed By: makovkastar Differential Revision: D91275922 fbshipit-source-id: 46fa6d887dd62f4f650d3a77468eb3e864275802
This commit is contained in:
committed by
meta-codesync[bot]
parent
89b8628f8c
commit
9553d7eabe
@@ -0,0 +1,178 @@
|
||||
# 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)
|
||||
@@ -1,36 +0,0 @@
|
||||
This tool is essentially a type checker, and as such it can be difficult to
|
||||
understand the data flow. Luckily, there are fairly extensive tests which can
|
||||
aid in ramping up.
|
||||
|
||||
This tool is made up of 3 primary stages: TypeDiffing, VersionDiffing, and
|
||||
ErrorFormatting.
|
||||
|
||||
At a high level, the schemas are passed to TypeDiffing which is the pure
|
||||
typechecker. It returns all differences between the types. VersionDiffing then
|
||||
interprets these results and decides if some of those changes are actually safe
|
||||
in the context of React Native’s JS/Native boundary.
|
||||
|
||||
For example, if you have a NativeModule method that returns a string union
|
||||
`small | medium | large`. Any changes to that union would be flagged by
|
||||
TypeDiffing as incompatible. However, adding a value to that union is safe
|
||||
because it ensures your JS code handles more cases than native returns. Removing
|
||||
a value from that union isn’t safe though because it means your JS no longer
|
||||
handles something native might return which could cause an exception.
|
||||
|
||||
VersionDiffing encodes the logic of what is safe and what isn’t;
|
||||
property/union/enum additions and removals, changing something from optional to
|
||||
required and vice versa, etc. VersionDiffing has knowledge of components and
|
||||
modules.
|
||||
|
||||
VersionDiffing returns a set of incompatible changes, which then gets passed to
|
||||
ErrorFormatting. ErrorFormatting does as you’d expect, converting these deep
|
||||
objects into nicely formatted strings.
|
||||
|
||||
When contributing, some principles:
|
||||
|
||||
- Keep TypeDiffing and ErrorFormatting pure. They should only know about
|
||||
JavaScript types, not React Native specific concepts
|
||||
- Add tests for every case you can think of. This codebase can be complex and
|
||||
hard to reason about when making changes. The only way to stay sane is to be
|
||||
able to rely on the tests to catch anything bad you’ve done. Do yourself and
|
||||
future contributors a favor.
|
||||
Reference in New Issue
Block a user