Repository navigation
tool: generate configuration properties reference - #2145
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new ConfigPropertiesReport.java has confirmed correctness/compilation issues (record component access + text-block handling + merging behavior) that will produce a broken or inaccurate report.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a new server-side developer script to generate a Markdown reference of Spring configuration properties by scanning @Value("${...}") and @ConfigurationProperties(...) usages, and aligns the server scripts/build tooling with Java 25.
Changes:
- Add
ConfigPropertiesReport.java(JBang) plus aconfig-properties-report.shwrapper to generate a configuration properties reference from source. - Update existing JBang scripts to declare
//JAVA 25+. - Bump
maven-artifactused by the dependency override checker and alignbuildSrccompilation to Java 25.
File summaries
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Updated output: |
There was a problem hiding this comment.
🟡 Changes recommended
The new tooling introduces correctness/build risks (Gradle Java compatibility configuration and regex constant-resolution ambiguity) that should be addressed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
server/scripts/src/ConfigPropertiesReport.java:214
- Same issue as in
scanValueAnnotations: this regex can match the constant name as a substring of another identifier and resolve the wrong@ConfigurationPropertiesprefix. Use identifier boundaries to ensure only the intended constant is matched.
var name = expr.contains(".") ? expr.substring(expr.lastIndexOf('.') + 1) : expr;
var constantValueMatcher =
Pattern.compile(Pattern.quote(name) + "\\s*=\\s*\"([^\"]*)\"").matcher(raw);
if (constantValueMatcher.find()) {
prefix = constantValueMatcher.group(1);
- Files reviewed: 7/7 changed files
- Comments generated: 2
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
The new ConfigPropertiesReport.java currently won’t compile due to record component field access and should also align its main signature with the repo’s JBang script convention.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
server/scripts/src/ConfigPropertiesReport.java:39
- All other JBang scripts in
server/scripts/srcdefinevoid main(String[] args), but this script definesvoid main()while the wrapper forwards "$@". To avoid surprising invocation behavior and stay consistent with the existing scripts, acceptString[] args(even if unused).
server/scripts/src/ConfigPropertiesReport.java:70
PropertyEntryis a record, so its components are not accessible as fields from here; usingentry.defaultValue,entry.sources,entry.note, andentry.keywill not compile. Use the record accessor methods instead (defaultValue(),sources(),note(),key()).
var defaultCell = entry.defaultValue == null || entry.defaultValue.isEmpty()
? "-"
: "`" + mdEscape(entry.defaultValue) + "`";
var sourceCell =
String.join(", ", entry.sources.stream().map(s -> "`" + s + "`").toList());
- Files reviewed: 7/7 changed files
- Comments generated: 0 new
- Review effort level: Lite
Adds a jbang script that scans src/main/java for @value("${...}") and @ConfigurationProperties(...) usages and generates a Markdown reference of every configuration property the server reads — key, default value, source location, and any @ConfigurationProperties prefix with its bound fields. There's no spring-boot-configuration-processor wired into this project, so there's currently no generated list of config properties to reference; this fills that gap without adding a build-time dependency. - server/scripts/src/ConfigPropertiesReport.java — jbang script (pure JDK, no external dependencies), following the existing convention in scripts/src/ (see DependencyOverrideCheck.java) - server/scripts/config-properties-report.sh — thin wrapper, mirrors dependency-override-check.sh Regex-based source scanning (no AST parser) that correctly handles the real edge cases present in this codebase: nested ${outer:${inner:default}} defaults, SpEL-wrapped @value expressions, string-concatenated @value args resolved against same-file constants, @ConfigurationProperties prefixes declared via a constant reference (RateLimitProperties.PROPERTY_PREFIX), and @ConfigurationProperties on @Bean-returned collections regardless of annotation order (MirrorConfig). Out of scope: cross-referencing application*.yml/.properties for actually-configured values, and recursing into @ConfigurationProperties nested field types beyond one level (e.g. RemoteScannerProperties's Map<String, ScannerConfig> keys are operator-chosen at runtime and aren't enumerable from source). Example output: https://gist.github.com/cstamas/f853eec0307f8d5977106bf69f505d29
As right now IDEA goes lala
- addEntry() now merges deterministically: whichever occurrence of a duplicate key provides a non-empty default/note wins, instead of the first-scanned occurrence silently shadowing a later one's data - blank text blocks before brace/paren scanning, so braces, parens, and semicolons inside one (e.g. SQL in an @query) can't throw off the structural scans - record each property's source relative to src/main/java instead of the bare filename, since it can be ambiguous across packages Confirmed unaffected: PropertyEntry/ConfigClassEntry record field access compiles fine here despite being flagged - this is a JEP 445 single-file source script, so the records are nested types of the implicit enclosing class and private field access between them is legal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
netomi
left a comment
There was a problem hiding this comment.
Rebased onto current main and reviewed. The tool works and the parsing is more careful than I
expected for a regex approach — the tricky cases in the PR description really do come out right
(nested ${outer:${inner:default}}, the SpEL-wrapped @Value, RateLimitProperties.PROPERTY_PREFIX,
MirrorConfig's annotation order). My findings are about completeness of the output rather than
correctness of the parsing.
Rebase
main had moved to 08ad497a3 (#2176), which already added //JAVA 25+ to the four scripts this PR
also touched — that was the conflict. Resolved as:
DependencyOverrideCheck.java— kept yourmaven-artifactbump to 3.9.16, withmain's
//JAVA-before-//DEPSordering.ImportSort.java— keptmain'sspotless-lib:4.10.2. Your side had 3.3.1, which predates #2176
moving it; restoring it would both undo that fix and fail the newformatter-version-check.sh.
The //JAVA 25+ churn on AddBracesFix/ClosingBraceFix/ImportSort is now redundant and dropped
out, so the diff went from 7 files to 4. MERGEABLE again. Force-pushed with --force-with-lease,
so nothing of yours could have been clobbered.
Verified on the rebased tree: spotlessCheck, formatter-version-check.sh, :buildSrc:compileJava,
and dependency-override-check.sh (confirming maven-artifact 3.9.16 works for its one consumer) all
pass, and the report itself runs and produces 206 keys.
Copilot's findings
Four of the six are already fixed on the branch: blankTextBlocks exists and runs first, source
locations are root.relativize(path), addEntry merges defaults/notes explicitly, and buildSrc uses
JavaVersion.VERSION_25.
One is simply wrong — the claim that field-style access on PropertyEntry "will not compile".
PropertyEntry is declared inside the implicit class, so its private components are accessible from
the enclosing class body. It compiles and runs. Worth not "fixing" on Copilot's say-so; the only real
point there is that the file mixes entry.key with entry.note(), which is a style inconsistency.
One is still open and correct: the constant lookup at both call sites has no identifier
boundaries, so Pattern.quote(name) + "\\s*=\\s*\"...\"" matches FOO inside MY_FOO. I checked
whether it currently misfires — adding \b on both sides produces byte-identical output, so it is
latent rather than active. Cheap to fix while it's cheap.
Findings of my own
1. @ConditionalOnProperty isn't scanned, and four real properties are missing as a result.
This is the one I'd want fixed. These keys are read only from @ConditionalOnProperty — no @Value
anywhere — so they appear nowhere in the reference:
| Property | Declared in |
|---|---|
ovsx.redis.enabled |
cache/CacheConfig.java |
ovsx.storage.migration.enabled |
storage/StorageMigration.java |
ovsx.upstream.proxy.enabled |
UpstreamProxyService.java |
spring.web.error.path |
web/ServerErrorController.java |
The first three are operator-facing switches that gate whole subsystems, which makes them among the
most important things such a document should list. @ConditionalOnProperty also supports
prefix = <constant> (RateLimitConfig uses RateLimitProperties.PROPERTY_PREFIX), so the
constant-resolution machinery already in the script is what's needed. The PR description lists what's
out of scope and this isn't in it, so I assume it was an oversight rather than a decision.
2. Keys that exist only as a nested fallback default are dropped. AccessTokenConfig has:
@Value("#{'${ovsx.access-token.prefix:${ovsx.token-prefix:}}'}")The report lists ovsx.access-token.prefix with default `${ovsx.token-prefix:}` but never lists
ovsx.token-prefix itself. That's the deprecated-key-fallback pattern AGENTS.md mandates for any
property that has shipped, so it's a recurring shape, and it's precisely the key an operator with the
old configuration needs to look up. PLACEHOLDER already captures the nested group; only the outer
match is used. (The other nested case, ovsx.trusted-publishing.audience:${ovsx.webui.url:}, is
covered only by luck — ovsx.webui.url has its own @Value elsewhere.)
3. @ConfigurationProperties field defaults are parsed and then thrown away. Around 30 of the 206
keys show - in the Default column while the source has a plain literal initializer. ovsx.rate-limit
is all eight of its keys:
private Boolean enabled = false;
private String tokenPrefix = "";
private String ipAddressFunction = "getRemoteAddr()";
private String defaultHttpContentType = "application/json";addField computes declaration.split("=", 2)[0] and discards the right-hand side. Passing a simple
literal through (and leaving expressions like List.of("svg") or ExtensionProcessor.DEFAULT_MAX_TAGS
as -, or resolving the latter with the constant lookup that already exists) would fill in the column
that makes the document useful. Not a bug, but the biggest cheap win available.
4. trimmed.contains("static") is a substring test. Same class of issue as the word boundaries: a
field like String staticPath would be silently skipped as though it were a static member. No such
field exists today, so latent — but it fails by omitting a property, which is the direction that
doesn't get noticed.
5. Minor: every field of a @ConfigurationProperties class reports the annotation's line rather
than the field's, so all five ovsx.publishing.* keys point at PublishingConfig.java:24. @Value
entries are line-accurate, so the inconsistency stands out. Fine if deliberate.
Also worth noting
maven-artifact is now a third "must match" pair (buildSrc/build.gradle and this script's //DEPS)
that nothing enforces — formatter-version-check.sh covers only jdt.core and spotless-lib, since
maven-artifact isn't a formatter dependency. Not for this PR, but the check could grow a generic
"these declarations must agree" mode.
Happy to push fixes for 1–4 to this branch if you'd rather not round-trip.
|
@netomi go for it, thanks! |
Addresses the review findings, with cstamas's go-ahead.
@ConditionalOnProperty is now scanned. Four properties were read only from that
annotation and so appeared in no reference at all - ovsx.redis.enabled,
ovsx.storage.migration.enabled, ovsx.upstream.proxy.enabled and
spring.web.error.path - three of which are operator-facing switches gating whole
subsystems. Only prefix/name/value are keys; havingValue and matchIfMissing are
not, so the attributes are read by name rather than by taking every string
literal in the annotation. A prefix given as a constant from another class
(RateLimitConfig) cannot be resolved from the file being scanned, and is
reported on stderr and skipped rather than emitted under a made-up key - that
one key is documented anyway from the properties class it belongs to.
A key that exists only as a nested fallback default was dropped: `${a:${b:}}`
declares b as well, which is the pattern AGENTS.md requires of any key that has
shipped, so ovsx.token-prefix - the old name of ovsx.access-token.prefix - is
exactly what an operator with old configuration comes here to look up.
@ConfigurationProperties field initializers were parsed and then discarded.
Plain literals are now reported, which fills in four of the empty Default cells;
expressions (List.of("svg"), a constant from another class) are still left blank
rather than printed as source an operator cannot use. Read from the raw text
rather than the blanked copy, which had replaced every string literal with x's -
the blanking passes all preserve length, so the two are index-aligned.
Both constant lookups were missing identifier boundaries, so FOO could resolve
against MY_FOO, and the class-body scan tested contains("static"), which would
skip a field named staticPath as though it were a static member. Both were
latent - the output is unchanged by either fix - and both fail by omitting a
property, which is the direction nobody notices.
Scanning the conditionals pushed one key's source list to two dozen entries, so
the cell now names three and counts the rest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Took you up on the go-ahead — findings 1–4 are in
Only One case cannot be resolved:
Nested fallback keys. Field defaults. I should correct my own review here: I said "around 30 keys" lose a recoverable default, and that was One subtlety worth knowing: the class-body scan works on the blanked copy, where string literals Both boundary fixes (the constant lookup, and One thing I caused and then fixed: scanning the conditionals pushed Net: 200 → 205 property keys, nothing removed. Report runs clean. Still open from my review, and deliberately not touched: |
This was the only script in scripts/src with a no-argument `void main()`; the other five take `String[] args`. jbang deduces the main class, and a version that does not recognise a no-argument instance main fails the script with "no main class deduced, specified nor found in a manifest" rather than anything that points at the cause. Not reproducible on jbang 0.141.0, where both forms run, so this is the portable form rather than a verified fix for one version. It costs nothing and removes the only structural difference from the scripts that do work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Adds a JBang script that scans
src/main/javafor@Value("${...}")and@ConfigurationProperties(...)usages and generates a Markdown reference of every configuration property the server reads — key, default value, source location, and any@ConfigurationPropertiesprefix with its bound fields.There's no spring-boot-configuration-processor wired into this project, and even if there would be, it generates incomplete list, so there's currently no generated list of config properties to reference; this fills that gap without adding a build-time dependency.
server/scripts/src/ConfigPropertiesReport.java— Java 25 JBang script (pure JDK, no external dependencies), following the existing convention inscripts/src/(seeDependencyOverrideCheck.javaas example)server/scripts/config-properties-report.sh— thin wrapper, mirrorsdependency-override-check.shRegex-based source scanning (no AST parser) that correctly handles the real edge cases present in this codebase: nested
${outer:${inner:default}}defaults, SpEL-wrapped@Valueexpressions, string-concatenated@Valueargs resolved against same-file constants,@ConfigurationPropertiesprefixes declared via a constant reference (RateLimitProperties.PROPERTY_PREFIX), and@ConfigurationPropertieson@Bean-returned collections regardless of annotation order (MirrorConfig).Out of scope: cross-referencing
application*.yml/.propertiesfor actually-configured values, and recursing into@ConfigurationPropertiesnested field types beyond one level (e.g.RemoteScannerProperties'sMap<String, ScannerConfig> keysare operator-chosen at runtime and aren't enumerable from source).Example output:
https://gist.github.com/cstamas/f853eec0307f8d5977106bf69f505d29
A bit unrelated: tidy up the
buildSrcsub-project to be Java 25 (as "outer build") as it now aligns the two, and IDEs like Idea are not confused about it anymore. Also JBang IDEA plugin now works just fine.