Skip to content

Commit fe5c3c9

Browse files
authored
Generate plugin reference docs and deploy via GitHub Pages (#436)
Extend DocumentationSyncTest to generate a Markdown reference page from RewriteExtension Javadoc and task descriptions. Configure the Javadoc task for full API docs, and add a GitHub Pages workflow to publish both.
1 parent aee7c33 commit fe5c3c9

4 files changed

Lines changed: 235 additions & 0 deletions

File tree

.github/workflows/pages.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
name: Deploy plugin docs to Pages
3+
4+
on:
5+
push:
6+
tags:
7+
- v*
8+
workflow_dispatch: {}
9+
workflow_run:
10+
workflows: ["publish"]
11+
types:
12+
- completed
13+
14+
permissions:
15+
contents: read
16+
pages: write
17+
id-token: write
18+
19+
concurrency:
20+
group: "pages"
21+
cancel-in-progress: false
22+
23+
jobs:
24+
pages:
25+
environment:
26+
name: github-pages
27+
url: ${{ steps.deployment.outputs.page_url }}
28+
runs-on: ubuntu-latest
29+
timeout-minutes: 30
30+
env:
31+
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}
32+
steps:
33+
- uses: actions/checkout@v6
34+
- uses: actions/setup-java@v5
35+
with:
36+
distribution: temurin
37+
java-version: 17
38+
39+
- name: Setup Gradle
40+
uses: gradle/actions/setup-gradle@v5
41+
with:
42+
develocity-access-key: ${{ env.GRADLE_ENTERPRISE_ACCESS_KEY }}
43+
44+
- name: Generate documentation
45+
run: ./gradlew :plugin:test --tests "org.openrewrite.gradle.DocumentationSyncTest" :plugin:javadoc
46+
47+
- name: Assemble site
48+
run: |
49+
mkdir -p site/apidocs
50+
cp docs/plugin-reference.md site/index.md
51+
cp -r plugin/build/docs/javadoc/* site/apidocs/
52+
53+
- uses: actions/configure-pages@v6
54+
- uses: actions/upload-pages-artifact@v4
55+
with:
56+
path: site
57+
- uses: actions/deploy-pages@v5

docs/plugin-reference.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# OpenRewrite Gradle Plugin Reference
2+
3+
Automatically eliminate technical debt. Apply OpenRewrite recipes to refactor, migrate, and fix source code across Java, Kotlin, Gradle, XML, YAML, properties, and more.
4+
5+
## Tasks
6+
7+
### `rewriteRun`
8+
9+
Apply the active refactoring recipes. Source files will be modified in place.
10+
11+
### `rewriteDryRun`
12+
13+
Run the active refactoring recipes, producing a patch file. No source files will be changed.
14+
15+
### `rewriteDiscover`
16+
17+
Lists all available recipes, their visitors, and active recipes configured in the rewrite DSL or rewrite.yml
18+
19+
## Configuration
20+
21+
The plugin is configured via the `rewrite` DSL block in your `build.gradle` or `build.gradle.kts`:
22+
23+
```groovy
24+
rewrite {
25+
activeRecipe("org.openrewrite.java.format.AutoFormat")
26+
exclusion("src/generated/**")
27+
}
28+
```
29+
30+
### Properties
31+
32+
| Property | Type | Default | Description |
33+
|----------|------|---------|-------------|
34+
| `activeRecipes` | `List<String>` | Empty list | Fully qualified class names of recipes to activate. Recipes will only run when explicitly activated here or in a rewrite.yml file. |
35+
| `activeStyles` | `List<String>` | Empty list | Fully qualified class names of styles to activate. Styles will only be applied when explicitly activated here or in a rewrite.yml file. |
36+
| `configFile` | `File` | `rewrite.yml` | Path to the OpenRewrite YAML configuration file. Defaults to `rewrite.yml` in the project directory. |
37+
| `checkstyleConfigFile` | `File` | `null` | Optional path to a Checkstyle configuration file. When set, OpenRewrite will use it to inform Java code style decisions. If not set explicitly, the plugin will attempt to auto-detect a Checkstyle configuration from the Checkstyle Gradle plugin. |
38+
| `enableExperimentalGradleBuildScriptParsing` | `boolean` | `true` | Whether to parse Gradle build scripts (`build.gradle`) as part of the source set. Defaults to `true`. |
39+
| `exportDatatables` | `boolean` | `false` | Whether to export data tables to `<build directory>/reports/rewrite/datatables/<timestamp>`. Defaults to `false`. |
40+
| `exclusions` | `List<String>` | Empty list | Glob patterns for files to exclude from processing. For example: `"src/generated/**"`. |
41+
| `plainTextMasks` | `List<String>` | Empty list | Glob patterns for files that should be parsed as plain text. Defaults to a comprehensive list including `**/*.md`, `**/*.sql`, `**/*.txt`, and others. Exclusions take precedence over plain text masks. |
42+
| `sizeThresholdMb` | `int` | `10` | Maximum file size in megabytes. Source files larger than this threshold are skipped during parsing. Defaults to `10`. |
43+
| `rewriteVersion` | `String` | `null` | Override the version of rewrite core libraries to be used. When `null`, the version bundled with the plugin is used. |
44+
| `logCompilationWarningsAndErrors` | `boolean` | `false` | Whether to log Java compilation warnings and errors encountered during parsing. Defaults to `false`. |
45+
| `failOnInvalidActiveRecipes` | `boolean` | `false` | Whether to throw an exception if an activeRecipe fails configuration validation. This may happen if the activeRecipe is improperly configured, or any downstream recipes are improperly configured. For the time, this default is "false" to prevent one improperly configured recipe from failing the build. In the future, this default may be changed to "true" to be more restrictive. |
46+
| `failOnDryRunResults` | `boolean` | `false` | Whether `rewriteDryRun` should fail the build when it detects that changes would be made. Useful in CI to enforce that all recipes have already been applied. Defaults to `false`. |
47+
| `throwOnParseFailures` | `boolean` | `false` | Whether to throw an exception when source file parsing fails. Can also be enabled via the project property `-Prewrite.throwOnParseFailures`. Defaults to `false`. |
48+
49+
## Javadoc
50+
51+
Full API documentation is available in the [Javadoc](apidocs/index.html).
52+

plugin/build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,15 @@ tasks.named("check").configure {
218218
dependsOn(testGradle4, testGradle8)
219219
}
220220

221+
tasks.withType<Javadoc>().configureEach {
222+
title = "OpenRewrite Gradle Plugin API"
223+
(options as StandardJavadocDocletOptions).apply {
224+
addStringOption("Xdoclint:none", "-quiet")
225+
links("https://docs.gradle.org/current/javadoc/")
226+
links("https://docs.oracle.com/en/java/javase/17/docs/api/")
227+
}
228+
}
229+
221230
configure<LicenseExtension> {
222231
ext.set("year", Calendar.getInstance().get(Calendar.YEAR))
223232
skipExistingHeaders = true

plugin/src/test/kotlin/org/openrewrite/gradle/DocumentationSyncTest.kt

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,121 @@ class DocumentationSyncTest {
7575
.isNotBlank()
7676
}
7777
}
78+
79+
private val taskClasses = listOf(
80+
"src/main/java/org/openrewrite/gradle/RewriteRunTask.java" to "rewriteRun",
81+
"src/main/java/org/openrewrite/gradle/RewriteDryRunTask.java" to "rewriteDryRun",
82+
"src/main/java/org/openrewrite/gradle/RewriteDiscoverTask.java" to "rewriteDiscover",
83+
)
84+
85+
@Test
86+
fun `generated plugin reference is up to date`() {
87+
val generated = generatePluginReference()
88+
val referenceFile = File("../docs/plugin-reference.md")
89+
90+
// Always write the file so it stays in sync
91+
referenceFile.parentFile.mkdirs()
92+
referenceFile.writeText(generated)
93+
}
94+
95+
private fun generatePluginReference(): String {
96+
val sb = StringBuilder()
97+
sb.appendLine("# OpenRewrite Gradle Plugin Reference")
98+
sb.appendLine()
99+
sb.appendLine("Automatically eliminate technical debt. Apply OpenRewrite recipes to refactor, migrate, and fix source code across Java, Kotlin, Gradle, XML, YAML, properties, and more.")
100+
sb.appendLine()
101+
sb.appendLine("## Tasks")
102+
sb.appendLine()
103+
104+
for ((path, taskName) in taskClasses) {
105+
val source = File(path).readText()
106+
val description = Regex("""setDescription\("(.+?)"\)""").find(source)!!.groupValues[1]
107+
sb.appendLine("### `$taskName`")
108+
sb.appendLine()
109+
sb.appendLine(description)
110+
sb.appendLine()
111+
}
112+
113+
sb.appendLine("## Configuration")
114+
sb.appendLine()
115+
sb.appendLine("The plugin is configured via the `rewrite` DSL block in your `build.gradle` or `build.gradle.kts`:")
116+
sb.appendLine()
117+
sb.appendLine("```groovy")
118+
sb.appendLine("rewrite {")
119+
sb.appendLine(" activeRecipe(\"org.openrewrite.java.format.AutoFormat\")")
120+
sb.appendLine(" exclusion(\"src/generated/**\")")
121+
sb.appendLine("}")
122+
sb.appendLine("```")
123+
sb.appendLine()
124+
sb.appendLine("### Properties")
125+
sb.appendLine()
126+
sb.appendLine("| Property | Type | Default | Description |")
127+
sb.appendLine("|----------|------|---------|-------------|")
128+
129+
// Match Javadoc comment followed by optional annotations and a field declaration.
130+
// The Javadoc must start at the beginning of a line (after optional whitespace).
131+
val javadocFieldPattern = Regex("""(?m)^\s*/\*\*\n((?:\s*\*.*\n)*?)\s*\*/\n(?:\s*@\w+(?:\(.*?\))?\s*\n)*\s*(?:private|public|protected)\s+([\w<>,\s]+?)\s+(\w+)\s*([;=].*)""")
132+
133+
for (match in javadocFieldPattern.findAll(extensionSource)) {
134+
val rawJavadoc = match.groupValues[1]
135+
val fieldType = match.groupValues[2].trim()
136+
val fieldName = match.groupValues[3]
137+
val rest = match.groupValues[4]
138+
val initializer = if (rest.startsWith("=")) {
139+
rest.drop(1).trimEnd(';').trim()
140+
} else {
141+
""
142+
}
143+
144+
if (fieldName !in userFacingFields) continue
145+
146+
// Clean up Javadoc: strip * prefixes, {@code ...} -> `...`, collapse whitespace
147+
val javadoc = rawJavadoc
148+
.lines()
149+
.joinToString(" ") { it.trimStart().removePrefix("* ").removePrefix("*").trim() }
150+
.replace(Regex("""\{@code\s+(.*?)\}"""), "`$1`")
151+
.replace(Regex("""\{@link\s+#?\S+\s+(.*?)\}"""), "$1")
152+
.replace(Regex("""\{@link\s+(.*?)\}"""), "`$1`")
153+
.replace(Regex("""\s*<p>\s*"""), " ")
154+
.replace(Regex("""\s*<pre>.*?</pre>\s*"""), " ")
155+
.replace("&#47;", "/")
156+
.replace(Regex("""\s+"""), " ")
157+
.trim()
158+
159+
// Determine display type
160+
val displayType = when {
161+
fieldType.contains("List<String>") -> "`List<String>`"
162+
fieldType.contains("boolean") -> "`boolean`"
163+
fieldType.contains("int") -> "`int`"
164+
fieldType.contains("String") -> "`String`"
165+
fieldType.contains("File") -> "`File`"
166+
else -> "`$fieldType`"
167+
}
168+
169+
// Determine default value from field initializer, with overrides for constructor-set fields
170+
val default = when (fieldName) {
171+
"configFile" -> "`rewrite.yml`"
172+
else -> when {
173+
initializer.startsWith("new ArrayList") -> "Empty list"
174+
initializer == "true" -> "`true`"
175+
initializer == "false" -> "`false`"
176+
initializer.matches(Regex("\\d+")) -> "`$initializer`"
177+
fieldType.contains("boolean") && initializer.isEmpty() -> "`false`"
178+
fieldType.contains("String") && initializer.isEmpty() -> "`null`"
179+
fieldType.contains("File") && initializer.isEmpty() -> "`null`"
180+
else -> ""
181+
}
182+
}
183+
184+
sb.appendLine("| `$fieldName` | $displayType | $default | $javadoc |")
185+
}
186+
187+
sb.appendLine()
188+
sb.appendLine("## Javadoc")
189+
sb.appendLine()
190+
sb.appendLine("Full API documentation is available in the [Javadoc](apidocs/index.html).")
191+
sb.appendLine()
192+
193+
return sb.toString()
194+
}
78195
}

0 commit comments

Comments
 (0)