Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
f732800
Seed referenced builders via field functions when no copy path exists
devin-ai-integration[bot] Oct 3, 2026
fc93b19
Cover remaining readable-property shapes and tighten getter detection
devin-ai-integration[bot] Oct 3, 2026
3fb31a1
Detect the builder's field-function prefix instead of hardcoding name…
devin-ai-integration[bot] Oct 3, 2026
6272479
Rank field-function prefixes by coverage, most findings first
devin-ai-integration[bot] Oct 3, 2026
0417bcb
Scale prefix trust threshold to the property count
devin-ai-integration[bot] Oct 3, 2026
4a9a227
Gate field-function seeding behind usingFieldFunctionSeeding (default…
devin-ai-integration[bot] Oct 3, 2026
664de50
Rename seeding to prefill, majority coverage, review fixes
devin-ai-integration[bot] Oct 3, 2026
5bdde2b
Rename option to usingFieldFunctionPrefill, enable by default
devin-ai-integration[bot] Oct 3, 2026
38f8a8b
Rename seeding vocabulary to prefill throughout
devin-ai-integration[bot] Oct 3, 2026
d5b005d
StringUtils.indexOfAny for prefix boundary, Predicate.not for filter
devin-ai-integration[bot] Oct 3, 2026
1b49d97
Fix Sonar findings: extract container unwrap, stream collectPrefilled…
devin-ai-integration[bot] Oct 3, 2026
dbe7406
Null-check type parameter in findReadableProperties (S2259)
devin-ai-integration[bot] Oct 3, 2026
cd40a00
Gate debug note, move field-function finder to analyser, pick best co…
devin-ai-integration[bot] Oct 3, 2026
4d98ec6
PrefillCall.Field naming, static factory, real builders in fixtures
devin-ai-integration[bot] Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,26 @@
*/
OptionState usingExistingBuilders() default OptionState.UNSET;

/**
* Prefill a reused builder that offers no value-accepting creation path by calling one of its
* field functions per readable property of the referenced type: the generated builder emits a
* private {@code prefill<Builder>(T value)} method that obtains an empty instance and calls the
* field function matching each property (fluent {@code name(v)}, JavaBeans {@code setName(v)},
* or a detected convention like {@code withName(v)}). Properties without a matching function
* stay unset; the convention is trusted once it covers the majority of the readable properties.
* <br>
* When disabled, such builders still resolve but helpers that would silently drop state are
* skipped. Only consulted when {@link #usingExistingBuilders()} is enabled; the analysis
* (property enumeration plus method scan per referenced type) is a separate option because it
* adds compile cost. {@link SimpleMinimalBuilder} deactivates this option.
*
* <p>Default: ENABLED <br>
* Compiler option: -Asimplebuilder.usingFieldFunctionPrefill
*
* @return the option state for prefilling builders through field functions
*/
OptionState usingFieldFunctionPrefill() default OptionState.UNSET;

// === Naming ===
/**
* Suffix to append to the DTO name to generate the builder class name. <br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
copyTypeAnnotations = OptionState.DISABLED,
implementsBuilderBase = OptionState.DISABLED,
usingExistingBuilders = OptionState.DISABLED,
usingFieldFunctionPrefill = OptionState.DISABLED,
builderSuffix = "Builder",
setterSuffix = "",
formattingMode = "lightweight"))
Expand Down
37 changes: 31 additions & 6 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -762,18 +762,27 @@ same-package candidate below. Accessible nested types satisfying the contract
are checked first (e.g. `Person.PersonBuilder` or `Person.Builder`), then a
static parameterless factory on the type — `builder()` preferred — whose return
type must be accessible and declare a no-arg `build()` method (Lombok/Immutables
style). The factory itself is the empty-instantiation path; the seeded path is
the builder's own contract path, a static `builder(T)`-style method, or an
instance method on the value like `toBuilder()`.
style). The factory itself is the empty-instantiation path; the prefilled path is
the builder's own contract path, a static `builder(T)`-style method, an
instance method on the value like `toBuilder()`, or — with
`usingFieldFunctionPrefill` enabled — prefilling by field functions.

The processor constructs the candidate builder name using `builderUsageSuffix`
(or `builderSuffix` if not configured) and verifies the builder contract: a way
to create an empty builder (a no-arg constructor or a static parameterless
factory like `create()`), a way to create one seeded with the value (a
factory like `create()`), a way to create one prefilled with the value (a
constructor accepting the referenced type or a static factory like `create(T)`
or `of(T)`), and a no-arg `build()` method returning it — each accessible from
the generated builder's package. Generated code calls a factory instead of
`new` when the builder offers one. Any class with the expected name and a
`new` when the builder offers one. With `usingFieldFunctionPrefill` enabled and
no prefilled path found, the builder is tried against field-function prefilling:
the generated builder emits a private static `prefill<Builder>` method that
prefills each readable property having a matching field function (`name(v)`,
`setName(v)`, or a detected convention like `withName(v)`); the convention is
trusted once it covers the majority of the properties, uncovered ones stay
unset. With less coverage or the option disabled, the builder still resolves,
but helpers that would silently drop state (builder consumers) are skipped and
a debug note is logged. Any class with the expected name and a
matching contract qualifies, allowing references to builders generated with
custom template annotations, external tools, or different suffixes. If the
candidate builder cannot be found, the field falls back to a plain setter.
Expand Down Expand Up @@ -805,6 +814,22 @@ type falls back to a plain setter, opting out of false-positive detections on
types that coincidentally look like builders. `@SimpleMinimalBuilder` disables
this option.

#### `usingFieldFunctionPrefill`

**Default**: `ENABLED` | **Compiler Option**: `-Asimplebuilder.usingFieldFunctionPrefill=DISABLED`

Prefills a reused builder that offers no value-accepting creation path by
calling one field function per readable property of the referenced type: the
generated builder emits a private static `prefill<Builder>(T value)` method
that obtains an empty instance and calls the field function matching each
property. The naming convention is detected on the builder — fluent `name(v)`
and JavaBeans `setName(v)` first, then any other camel-case prefix shared by
several methods (e.g. `withName(v)`); a convention is trusted once it covers
the majority of the properties, matched param-type-exact, void- or
fluent-returning. Only consulted when `usingExistingBuilders` is enabled; the
per-type analysis (property enumeration plus method scan) is a separate option
because it adds compile cost. `@SimpleMinimalBuilder` disables this option.

### Component Filtering

#### `deactivateGenerationComponents`
Expand Down Expand Up @@ -1065,7 +1090,7 @@ the suffix used for own builder generation.

The candidate class must provide a way to create an empty builder (a no-arg
constructor or a static parameterless factory like `create()`), a way to create
one seeded with the value (a constructor accepting the referenced type or a
one prefilled with the value (a constructor accepting the referenced type or a
static factory like `create(T)` or `of(T)`), and a no-arg `build()` method
returning it — each accessible from the generated builder's package. Generated
code calls a factory instead of `new` when the builder offers one. The contract
Expand Down
Loading
Loading