Skip to content

Add enum constants for field options in generated block types - #8

Merged
nadar merged 4 commits into
mainfrom
claude/wizardly-cori-ytz793
Oct 12, 2026
Merged

nadar merged 4 commits into
mainfrom
claude/wizardly-cori-ytz793

Conversation

@nadar

@nadar nadar commented Oct 12, 2026

Copy link
Copy Markdown
Member

Summary

This change adds automatic generation of PHP constants for every fixed option in content and config fields of block classes. Each option becomes a class constant named <CONTENT|CONFIG>_<FIELD>_<OPTION> with the option value, enabling type-safe matching against field values.

Key Changes

  • New EnumConstants class (lib/Generator/EnumConstants.php): Analyzes block schemas to identify fields with fixed option sets (both direct enum fields and select fields) and generates ConstantDef objects for each option. Handles collision detection when multiple options would generate the same constant name, emitting warnings instead of silently numbering them.

  • Updated TypeMapper: Extracted enum member processing into a public static enumMembers() method to support both type generation and constant naming. Added validation to reject enums containing */ (which would break docblocks) or newlines, falling back to plain string type.

  • Modified BlockKind: Added optional $enumConstants parameter (defaults to true) to control constant generation. The deprecated flyo-generate-blocks command disables it to maintain backward compatibility, while flyo-generate-types enables it.

  • Enhanced Renderer: Updated constant rendering to group related constants (e.g., all options of one field) without blank lines between them, with blank lines separating different fields.

  • Updated Naming: Added constantSegment() method to convert field names and option values to upper snake case suitable for constant names, handling kebab-case, camelCase, and special characters.

  • Comprehensive test coverage: Added EnumConstantsTest with 20+ test cases covering select fields, collision detection, invalid options, and edge cases. Added fixture with OpenAPI schema and expected output demonstrating the feature.

Notable Implementation Details

  • Constants are only generated for content and config object properties, not nested structures
  • Select fields (objects with a value property) have constants named after the field, not the value property
  • Option values are preserved exactly as specified in the schema, enabling type-safe literal matching
  • Numeric options in string enums become string constants (e.g., '2' not 2)
  • Collisions are detected across fields (e.g., content.size "small_wide" and content.size_small "wide" both want CONTENT_SIZE_SMALL_WIDE)
  • The deprecated flyo-generate-blocks command output remains unchanged for backward compatibility

https://claude.ai/code/session_01SjJHX6UBVjHdxaoMgHLZYC

nadar and others added 4 commits October 12, 2026 14:52
Every content and config field of a block whose type is a union of
literals, a select field's `value` included, gets one constant per
option, named `<CONTENT|CONFIG>_<FIELD>_<OPTION>`:
`CONTENT_SIZE_SMALL = 'small'`, `CONFIG_SIZE_SMALL = 'small'`. The
values are the strings TypeMapper writes into the field's type, so
comparing a field with a constant type-checks wherever comparing it
with the literal does. A name that would stand for two different
options is not generated, with a warning, rather than numbered: a
number would silently move to another option once one is added.

Only flyo-generate-types generates them. The deprecated
flyo-generate-blocks keeps producing byte-identical files.

An enum member containing `*/` closed the docblock its type is
rendered into, so the generated file did not parse. Such an enum now
falls back to `string`, like one with a newline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjJHX6UBVjHdxaoMgHLZYC
A types/enums fixture with select and plain enum fields in content and
config, its golden file, and level 9 PHPStan assertions: a match over a
field with its constants as arms is only exhaustive when the constants
cover exactly the literals of the field's type.

Unit tests for EnumConstants (detection, naming, collisions, fields
that get none), Naming::constantSegment(), TypeMapper::enumMembers(),
hostile option values, and that the deprecated command generates no
enum constants.

README and UPGRADE describe the constants and the `*/` fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjJHX6UBVjHdxaoMgHLZYC
…e constants

The remaining files of the previous commit: TypeMapperTest gains the
`*/` case and a direct test of enumMembers(), DegenerateSchemaTest
checks that quotes, backslashes, `?>`, `$`, tabs and umlauts in options
still render valid constants, and that an option with `*/` falls back
to `string` without constants. README and UPGRADE describe both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjJHX6UBVjHdxaoMgHLZYC
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjJHX6UBVjHdxaoMgHLZYC
@nadar
nadar merged commit 8aade3d into main Oct 12, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant