feat: resolve record components as DTO fields - #153
Merged
Merged
Conversation
The Java parser only walked class_declaration and interface_declaration, so a record used as a request or response DTO contributed zero fields: record components are formal_parameter nodes under the declaration's formal_parameters, not field_declaration nodes, and nothing looked for record_declaration at all. A DTO written as the now-standard `public record CreateUserRequest(String name, String email, int age)` exported an empty request body. Teach the parser records: - extractor.go: extractRecord reads the name, annotations, type parameters, and implements clause off record_declaration (records reuse super_interfaces and type_parameters, so the existing extractors apply unchanged), and extractRecordComponents converts formal_parameter components to fields by reusing extractParameter, so component annotations and doc comments come through like method parameters. IsFinal stays unset even though components are implicitly final because the resolver skips final fields (#141 covers that rule itself) — marking them would have dropped every record field. The optional record body (compact constructors, methods, static members) is ignored rather than parsed. - parser.go: record_declaration joins class and interface in extractAllClasses. Records register in the class registry like any class, so generic records resolve through the existing type-parameter binding: PageResponse<T>(List<T> items, int total) requested as PageResponse<Order> yields items as an array of Order objects. Tests: fixtures for a plain record, a generic record, and a record with bean-validation annotations plus a compact constructor; parser tests over those fixtures; resolver tests for plain and generic record resolution; and a springmvc test parsing real source where a record as @RequestBody exports name, email, and age in the request body.
|
📦 Build artifact for this PR is available in the GitHub Actions workflow run under Artifacts. |
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.
Summary
Closes #139.
The Java parser only walked
class_declarationandinterface_declarationnodes, so a record used as a request or response DTO contributed zero fields: record components areformal_parameternodes under the declaration'sformal_parameters, notfield_declarationnodes, and nothing looked forrecord_declarationat all. A DTO written in the now-standard formexported a request body with no fields. Records are the default DTO style in modern Java codebases, so this silently affected a large share of real projects.
This PR teaches the parser records and lets them resolve through the existing class registry and type resolver unchanged.
Changes
api-collector-java/parser/extractor.goextractRecordreads the name, annotations, type parameters, andimplementsclause offrecord_declaration— records reuse the samesuper_interfacesandtype_parametersnodes as classes, so the existing extractors apply unchanged. NewextractRecordComponentsconvertsformal_parametercomponents to fields by reusingextractParameter, so component annotations and doc comments come through exactly like method parameters.IsFinalstays unset even though components are implicitly final, because the resolver skips final fields (#141 covers that rule itself) — marking them would have dropped every record field again. The optional record body (compact constructors, methods, static members) is ignored rather than parsed, so it cannot cause parse failuresapi-collector-java/parser/parser.gorecord_declarationjoins class and interface inextractAllClassesapi-collector-java/testdata/CreateUserRequest.javaapi-collector-java/testdata/PageResponseRecord.javaPageResponseRecord<T>(List<T> items, int page, int total))api-collector-java/testdata/ValidatedUserRecord.javaapi-collector-java/parser/parser_test.goTestParser_ParseRecordover the three fixtures: field names/types, package, type parameters, component annotations (@NotNull,@Size(min, max)), record JavaDoc, and proof that the compact constructor and body method do not become fieldsapi-collector-java/resolver/resolver_test.goTestResolve_SimpleRecord(record resolves to an object model withname/email/age) andTestResolve_GenericClassRecord(PageResponse<Order>bindsTsoitemsresolves to an array ofOrderobjects with their own fields)api-collector-java/springmvc/parser_test.goTestParser_RecordRequestBody: end-to-end over real source — a controller taking a record as@RequestBodyexports a request body schema withname,email, andageRecords register in the class registry like any class, so generic records resolve through the existing type-parameter binding machinery with no resolver changes.
Verification
go test ./...passes for all 13 workspace modules, andgo vet ./...is clean forapi-collector-java.public record CreateUserRequest(String name, String email, int age)as@RequestBodyexports a request body withname,email, andage(TestParser_RecordRequestBody).