UNPKG

@greenlabs/ppx-spice

Version:

ReScript PPX which generate JSON (de)serializer

233 lines (180 loc) 6.13 kB
# AGENTS.md This document provides guidelines for AI coding agents working on the ppx_spice project. ## Project Overview ppx_spice is a ReScript PPX that generates JSON (de)serializers. It is written in OCaml and uses ppxlib for AST manipulation. The PPX processes ReScript type definitions annotated with `@spice` and generates corresponding `_encode` and `_decode` functions. ## Build Commands All OCaml build commands must be run from the `src/` directory: ```bash # Navigate to source directory cd src # Create opam switch (first time setup) opam switch create spice 4.14.0 # Install dependencies opam install . --deps-only # Build the PPX dune build # Build with static linking (for releases) dune build --profile static # Clean build artifacts dune clean ``` ## Test Commands Tests are written in ReScript and located in the `test/` directory: ```bash # Navigate to test directory cd test # Install dependencies pnpm install # Build ReScript code pnpm res:build # Clean and rebuild pnpm res:clean && pnpm res:build # Run all tests pnpm test # Run tests in watch mode pnpm test:watch # Run a single test file pta './test/__tests__/spec/<test_name>_test.mjs' | tap-difflet # Example: Run only records tests pta './test/__tests__/spec/records_test.mjs' | tap-difflet ``` ### Development Workflow 1. Make changes to OCaml code in `src/ppx/` 2. Run `dune build` in `src/` 3. Navigate to `test/` and run `pnpm res:clean && pnpm res:build` 4. Run `pnpm test` to verify changes ## Project Structure ``` ppx_spice/ ├── src/ppx/ # OCaml PPX implementation │ ├── ppx_spice.ml # Entry point, registers transformation │ ├── codecs.ml # Primitive type codec generation │ ├── records.ml # Record encoder/decoder generation │ ├── variants.ml # Variant type handling │ ├── polyvariants.ml # Polymorphic variant handling │ ├── structure.ml # Implementation (.ml) processing │ ├── signature.ml # Interface (.mli) processing │ └── utils.ml # Shared utilities ├── src/bin/bin.ml # Executable entry point ├── test/src/ # ReScript test source types └── test/test/__tests__/ # Test specifications ``` ### PPX Integration (rescript.json) ```json { "ppx-flags": ["@greenlabs/ppx-spice/ppx"], "warnings": { "error": true, "number": "-48" } } ``` ## Code Style Guidelines ### OCaml Code Style #### Formatting - Use 2 spaces for indentation - Use LF line endings - Insert final newline at end of files - Maximum line length: ~100 characters (soft limit) #### Imports Always open modules in this order at the top of each file: ```ocaml open Ppxlib open Parsetree open Ast_helper open Utils (* Local utilities last *) ``` #### Type Definitions - Define types at the top of the module, after opens - Use record types for complex data structures: ```ocaml type parsed_decl = { name : string; key : expression; field : expression; codecs : expression option * expression option; } ``` #### Naming Conventions - Module names: PascalCase (`Records`, `Variants`, `Polyvariants`) - Function names: snake_case (`generate_encoder`, `parse_decl`) - Type names: snake_case (`parsed_decl`, `generator_settings`) - Variables: snake_case (`type_name`, `param_names`) - Constants/suffixes: snake_case with descriptive names - `encoder_func_suffix = "_encode"` - `decoder_func_suffix = "_decode"` #### Error Handling - Use `Result` type for operations that can fail - Use `fail` function from `Utils` to raise location-aware errors: ```ocaml let fail loc message = Location.raise_errorf ~loc "%s" message (* Usage *) | Ptyp_any -> fail ptyp_loc "Can't generate codecs for `any` type" ``` #### Pattern Matching - Prefer exhaustive pattern matching - Use `_` prefix for intentionally unused variables - Group related cases together: ```ocaml match ptype_kind with | Ptype_abstract -> (* handle abstract *) | Ptype_variant decls -> (* handle variant *) | Ptype_record decls -> (* handle record *) | _ -> fail ptype_loc "This type is not handled by spice" ``` #### PPX-Specific Patterns - Use ppxlib metaquot for AST construction: `[%expr ...]`, `[%pat? ...]`, `[%type: ...]` - Always attach `res.arity` attribute for ReScript function compatibility - Use `Utils.expr_func ~arity:1` for single-argument functions ### ReScript Test Code Style #### Test Structure ```rescript open Zora zoraBlock("descriptive test block name", t => { // Setup let sampleJson = ... let expected = ... // Execute let result = SomeModule.function(input) // Assert t->test("assertion description", async t => { t->equal(result, expected, "message") }) }) ``` #### Naming - Test files: `<module>_test.res` (e.g., `records_test.res`) - Test blocks: Descriptive phrases in quotes ## Dependencies ### OCaml Dependencies (from opam) - `ocaml` >= 4.14.0, <= 4.14.2 - `dune` >= 2.8 - `ppxlib` = 0.28.0 ### ReScript Dependencies (test only) - `rescript` ^12.0.0 - `@dusty-phillips/rescript-zora` ^4.0.0 ## Common Patterns ### Generated Function Names The PPX generates functions with these naming patterns: - Encoder: `<type_name>_encode` - Decoder: `<type_name>_decode` ### Attribute Handling ```ocaml (* Check for attribute *) match get_attribute_by_name attributes "spice.key" with | Ok (Some attr) -> (* attribute present *) | Ok None -> (* attribute absent *) | Error s -> fail loc s ``` ### Codec Generation Flow 1. `ppx_spice.ml` registers the transformation 2. `structure.ml` / `signature.ml` process type declarations 3. `codecs.ml` handles primitive types 4. `records.ml`, `variants.ml`, `polyvariants.ml` handle complex types 5. Generated AST is returned to the compiler ## CI/CD GitHub Actions workflows are in `.github/workflows/`: - `build_linux.yml` - Linux build with Alpine container - `build_macos.yml` - macOS build - `build_windows.yml` - Windows build - `publish.yml` - NPM package publishing Builds use OCaml 4.14.2 with static linking for portable binaries.