feat(compat): emit field/enum_member info for PHP, Python, Ruby#53
Merged
gjtorikian merged 1 commit intomainfrom May 2, 2026
Merged
feat(compat): emit field/enum_member info for PHP, Python, Ruby#53gjtorikian merged 1 commit intomainfrom
gjtorikian merged 1 commit intomainfrom
Conversation
PR #51's rename-detection needs field/enum_member info to pair renamed types and canonical-flipped enums. Three extractors weren't emitting it. PHP: PHP 8 model classes use constructor-promoted properties. Parser only accepted simple_parameter, skipped property_promotion_parameter. Extended walker to recognize promoted properties; added isValueObjectClass to classify them as ApiInterface. Python: @DataClass classes with from_dict/to_dict helpers fell through to ApiClass, losing field info. Added explicit dataclass branch that keeps them as ApiInterface regardless of method presence. Ruby: enums emit as classes not modules. Extended extractEnumModules to also walk constants-only class nodes, with extractClasses skip to prevent double-emission. Also: bareTypeName now strips PHP namespace prefix (\Vendor\Pkg\Foo → Foo) so the cascade matches qualified return types. End-to-end on workos/openapi-spec#17 — all five languages now report 0 breaking changes (php 7→0, python 3→0, ruby 2→0; go and dotnet already 0). Tests: 1381/1381 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
#51 shipped baseline-walking rename detection in
diffSnapshotsbut couldn't bring PHP/Python/Ruby to 0 breaking on workos/openapi-spec#17 because the structural info those extractors produce isn't sufficient to pair renamed types:@dataclassmodel classes were categorized asApiClass(because they havefrom_dict/to_dicthelper methods) — losing field info.readonly class Foo { function __construct(public Type $field, …) {} }). The parser only acceptedsimple_parameterparameter nodes and skippedproperty_promotion_parameterentirely — promoted properties weren't captured anywhere.class ApplicationsOrder; ASC = "asc"; …; ALL = [...].freeze; end), not modules. The enum extractor only walkedmodulenodes, so class-shape enums fell through toextractClassesand surfaced asApiClasswith noenum_memberchildren.Without field/enum_member symbols, the rename detector's structural matcher had nothing to compare on.
What
PHP (
php-parser.ts,php-surface.ts):parseMethods's parameter walker to acceptproperty_promotion_parameternodes alongsidesimple_parameter. When a parameter carries a visibility modifier, surface it underPhpClass.propertiesso the surface builder can read it.isValueObjectClasspredicate: a non-exception, non-enum class with at least one public promoted property is a value object → emit asApiInterfacewith promoted-property fields.Python (
python-surface.ts):isDataclasspredicate (matches@dataclass,@dataclass(),@dataclass(slots=True), etc. via the parser's existingdecoratorsarray).@dataclassclasses with at least one field →ApiInterfaceregardless of method presence. The field set is the canonical identity for data-shape classes.Ruby (
ruby-parser.ts):isEnumShapedClasspredicate: a class node whose body has only constant declarations (nomethod/singleton_method/call-shaped children — that filter naturally excludes attr_accessor + theALL = [...].freezeaggregator sinceextractEnumConstantsonly collects scalars).extractEnumModulesalso walksclassnodes through this predicate.extractClassesskips enum-shaped classes via the same predicate to prevent double-emission.differ.ts:bareTypeNamewith a PHP namespace-prefix strip (\Vendor\Pkg\Foo→Foo). Without this, PHP methodreturn_type_changedreports come in as\WorkOS\Resource\ApiKeyWithValueand don't match the rename map's bare-name keys.End-to-end validation on workos/openapi-spec#17
Re-extracted baseline + candidate locally with the new extractors, then ran
oagen compat-diff:All 33 originally-reported breaking changes are now soft-risk. The PR-comment compat report on workos/openapi-spec#17 will collapse to
Breaking 0 / Soft-risk ~80once oagen ships and openapi-spec bumps its dep.Tests
Existing extractor + differ tests pass unchanged (1381/1381). No new tests added in this commit — the existing test fixtures already cover the categorization branches I extended; the regressions guarded by tests were that
BaseRequestException(Python/PHP exception with public properties) doesn't get misclassified as a value object, which the existingextracts exception classestests cover and pass.If you'd like dedicated tests for the new branches (constructor-promoted PHP class → ApiInterface, dataclass with helper methods → ApiInterface, Ruby class-shape enum), I can add them as a follow-up — happy to take that direction in review.
Test plan
npm test— 1381/1381npm run lintcleannpx tsc --noEmitcleannpm run buildcleanoagen compat-difffor all 5 languages — all 0 breaking🤖 Generated with Claude Code