This is an automated email from the ASF dual-hosted git repository.
GGraziadei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/storm.git
The following commit(s) were added to refs/heads/master by this push:
new be45c93b4 Add checkstyle auto-fix tooling (#9017)
be45c93b4 is described below
commit be45c93b48f8ad05a06bb12f2af68dc5b944b5a1
Author: sergio <[email protected]>
AuthorDate: Sun Oct 18 15:20:51 2026 +0200
Add checkstyle auto-fix tooling (#9017)
* Add OpenRewrite + Groovy checkstyle auto-fix tooling (run on demand)
OpenRewrite is preferred: everything it has a recipe for (fully or partly)
runs
there, driven by a style in rewrite.yml that mirrors storm_checkstyle.xml.
The
Groovy script only keeps the rules with no recipe (LineLength,
OneStatementPerLine,
SeparatorWrap, Javadoc rules, member ordering, trivial text rules). A second
OpenRewrite execution re-indents after the Groovy stage. A Groovy script
normalizes
license headers to the ASF template, leaving third-party works alone.
Wired into the root and module poms without binding it to validate.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
* Add a workflow_dispatch GitHub Action to run the checkstyle auto-fix per
module
Takes the module as input, runs OpenRewrite, the Groovy stage and the
re-indent
pass plus the license header normalization on it, and pushes the result to
checkstyle-fix/<module> so one pull request per module can be opened
against master.
---------
Co-authored-by: Claude Sonnet 5 <[email protected]>
---
.github/workflows/checkstyle-autofix.yaml | 121 ++
.mvn/.gitkeep | 0
.mvn/jvm.config | 11 +
dev-tools/gitact/checkstyle-report.sh | 61 +
examples/storm-hdfs-examples/pom.xml | 10 +
examples/storm-jdbc-examples/pom.xml | 10 +
examples/storm-jms-examples/pom.xml | 10 +
examples/storm-kafka-client-examples/pom.xml | 10 +
examples/storm-loadgen/pom.xml | 10 +
examples/storm-perf/pom.xml | 10 +
examples/storm-redis-examples/pom.xml | 10 +
examples/storm-starter/pom.xml | 10 +
external/storm-autocreds/pom.xml | 10 +
external/storm-blobstore-migration/pom.xml | 10 +
external/storm-hdfs-blobstore/pom.xml | 10 +
external/storm-hdfs-oci/pom.xml | 10 +
external/storm-hdfs/pom.xml | 10 +
external/storm-jdbc/pom.xml | 10 +
external/storm-jms/pom.xml | 10 +
external/storm-kafka-client/pom.xml | 10 +
external/storm-kafka-migration/pom.xml | 10 +
external/storm-kafka-monitor/pom.xml | 10 +
external/storm-metrics/pom.xml | 10 +
external/storm-redis/pom.xml | 10 +
flux/flux-core/pom.xml | 10 +
flux/flux-examples/pom.xml | 10 +
flux/flux-wrappers/pom.xml | 10 +
flux/pom.xml | 10 +
integration-test/pom.xml | 10 +
pom.xml | 144 +-
rewrite.yml | 202 +++
storm-checkstyle/README.md | 144 ++
.../main/resources/storm/checkstyle-autofix.groovy | 1633 ++++++++++++++++++++
.../storm/normalize-license-headers.groovy | 176 +++
.../src/main/resources/storm/storm_checkstyle.xml | 9 +
storm-client/pom.xml | 10 +
storm-core/pom.xml | 10 +
storm-multilang/javascript/pom.xml | 10 +
storm-multilang/ruby/pom.xml | 10 +
storm-server/pom.xml | 10 +
storm-submit-tools/pom.xml | 10 +
storm-webapp/pom.xml | 10 +
42 files changed, 2818 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/checkstyle-autofix.yaml
b/.github/workflows/checkstyle-autofix.yaml
new file mode 100644
index 000000000..267ff585a
--- /dev/null
+++ b/.github/workflows/checkstyle-autofix.yaml
@@ -0,0 +1,121 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Runs the checkstyle auto-fix (storm-checkstyle/README.md) on one module,
pushes the result to a
+# new branch checkstyle-fix/<module>-<run id>-<attempt> (one per execution)
and opens a pull request
+# from it against the branch the workflow ran on. The Checkstyle report of the
module before and
+# after the fix is attached to the run as an artifact; the violations left
after the fix are listed
+# in the pull request.
+name: Checkstyle auto-fix
+
+on:
+ workflow_dispatch:
+ inputs:
+ module:
+ description: 'Maven module to fix, as its path in the repository (e.g.
storm-client or external/storm-hdfs)'
+ required: true
+ type: string
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ autofix:
+ runs-on: ubuntu-latest
+ timeout-minutes: 120
+ env:
+ MODULE: ${{ inputs.module }}
+ steps:
+ - name: Validate the module input
+ run: |
+ if ! [[ "$MODULE" =~ ^[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)*$ ]] || [[
"$MODULE" == *..* ]]; then
+ echo "::error::'$MODULE' is not a valid module path"
+ exit 1
+ fi
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #
v7.0.1
+ - name: Check that the module exists
+ run: test -f "$MODULE/pom.xml" || { echo "::error::$MODULE/pom.xml not
found"; exit 1; }
+ - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Set up JDK 25
+ uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 #
v6.0.1
+ with:
+ distribution: temurin
+ java-version: 25
+ - name: Check that the module declares the auto-fix plugins
+ run: |
+ # without them the auto-fix executions would run with no
configuration and silently fix nothing
+ mvn --batch-mode --quiet -pl "$MODULE" help:effective-pom
-Doutput="$RUNNER_TEMP/effective-pom.xml"
+ python3 - "$RUNNER_TEMP/effective-pom.xml" <<'EOF'
+ import sys
+ import xml.etree.ElementTree as ET
+ ns = {'m': 'http://maven.apache.org/POM/4.0.0'}
+ plugins = {p.text for p in
ET.parse(sys.argv[1]).getroot().findall('m:build/m:plugins/m:plugin/m:artifactId',
ns)}
+ missing = {'rewrite-maven-plugin', 'gmavenplus-plugin'} - plugins
+ if missing:
+ sys.exit(f"::error::the module does not declare {',
'.join(sorted(missing))} in its build plugins")
+ EOF
+ - name: Build the module and the modules it depends on (the Groovy stage
resolves its classpath)
+ run: mvn --batch-mode install -DskipTests -Dcheckstyle.skip=true -pl
"$MODULE" -am
+ - name: Checkstyle report before the fix
+ run: ./dev-tools/gitact/checkstyle-report.sh before
+ - name: Auto-fix the module (OpenRewrite, Groovy, OpenRewrite re-indent)
+ run: |
+ mvn --batch-mode -pl "$MODULE" -Dcheckstyle.skip=true \
+
org.openrewrite.maven:rewrite-maven-plugin:run@checkstyle-autofix-openrewrite \
+
org.codehaus.gmavenplus:gmavenplus-plugin:execute@checkstyle-autofix \
+
org.openrewrite.maven:rewrite-maven-plugin:run@checkstyle-reindent-openrewrite
+ - name: Normalize the license headers
+ run: mvn --batch-mode -pl "$MODULE" -Dcheckstyle.skip=true
org.codehaus.gmavenplus:gmavenplus-plugin:execute@normalize-license-headers
+ - name: Checkstyle report after the fix
+ run: ./dev-tools/gitact/checkstyle-report.sh after
+ - name: Upload the Checkstyle reports
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
# v7.0.1
+ with:
+ name: checkstyle-reports
+ path: ${{ runner.temp }}/checkstyle-reports/
+ if-no-files-found: ignore
+ - name: Push the result to a new branch and open a pull request
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ # one branch per execution, so a run never overwrites the branch of
an earlier one (or its open PR)
+ BRANCH="checkstyle-fix/$MODULE-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
+ # only the module itself (build output is git-ignored), never the
tooling files
+ git add -A -- "$MODULE"
+ if git diff --cached --quiet; then
+ echo "Nothing to fix in $MODULE" | tee -a "$GITHUB_STEP_SUMMARY"
+ exit 0
+ fi
+ git config user.name "github-actions[bot]"
+ git config user.email
"41898282+github-actions[bot]@users.noreply.github.com"
+ git checkout -b "$BRANCH"
+ git commit -m "Checkstyle auto-fix of $MODULE"
+ git push origin "$BRANCH"
+ REPORTS="$RUNNER_TEMP/checkstyle-reports"
+ {
+ echo "Checkstyle auto-fix of \`$MODULE\`, made by [this workflow
run]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) (the
full reports are in its \`checkstyle-reports\` artifact)."
+ echo
+ cat "$REPORTS/before.md" "$REPORTS/after.md"
+ } > "$RUNNER_TEMP/pr-body.md"
+ PR=$(gh pr create --base "$GITHUB_REF_NAME" --head "$BRANCH" \
+ --title "Checkstyle auto-fix of $MODULE" --body-file
"$RUNNER_TEMP/pr-body.md")
+ echo "Opened $PR from $BRANCH" | tee -a "$GITHUB_STEP_SUMMARY"
diff --git a/.mvn/.gitkeep b/.mvn/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
new file mode 100644
index 000000000..3d61a1f27
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1,11 @@
+--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/dev-tools/gitact/checkstyle-report.sh
b/dev-tools/gitact/checkstyle-report.sh
new file mode 100755
index 000000000..358dad416
--- /dev/null
+++ b/dev-tools/gitact/checkstyle-report.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Runs the build's Checkstyle check on $MODULE and saves its report as
+# $RUNNER_TEMP/checkstyle-reports/<label>.xml (and the console output as
<label>.txt), plus
+# <label>.md: the violation counts and a table of the violations per check,
which is also added to
+# the job summary. Used by .github/workflows/checkstyle-autofix.yaml.
+# The check failing on violations is expected: only a missing report fails
this script.
+
+set -u
+
+LABEL=$1
+OUT_DIR="$RUNNER_TEMP/checkstyle-reports"
+REPORT="$MODULE/target/checkstyle-violation.xml"
+mkdir -p "$OUT_DIR"
+rm -f "$REPORT"
+
+mvn --batch-mode -pl "$MODULE"
org.apache.maven.plugins:maven-checkstyle-plugin:check@validate \
+ > "$OUT_DIR/$LABEL.txt" 2>&1
+
+if [[ ! -f "$REPORT" ]]; then
+ tail -n 50 "$OUT_DIR/$LABEL.txt"
+ echo "::error::Checkstyle did not write $REPORT"
+ exit 1
+fi
+cp "$REPORT" "$OUT_DIR/$LABEL.xml"
+
+python3 - "$REPORT" "$LABEL" > "$OUT_DIR/$LABEL.md" <<'EOF'
+import collections
+import sys
+import xml.etree.ElementTree as ET
+
+report, label = sys.argv[1:]
+violations = list(ET.parse(report).getroot().iter('error'))
+severities = collections.Counter(v.get('severity') for v in violations)
+print(f"### Checkstyle {label} the fix: {severities['error']} errors,
{severities['warning']} warnings")
+if violations:
+ print()
+ print('| Check | Severity | Violations |')
+ print('|---|---|---|')
+ checks = collections.Counter((v.get('source').rsplit('.',
1)[-1].removesuffix('Check'), v.get('severity'))
+ for v in violations)
+ for (check, severity), count in checks.most_common():
+ print(f'| {check} | {severity} | {count} |')
+print()
+EOF
+cat "$OUT_DIR/$LABEL.md"
+cat "$OUT_DIR/$LABEL.md" >> "$GITHUB_STEP_SUMMARY"
diff --git a/examples/storm-hdfs-examples/pom.xml
b/examples/storm-hdfs-examples/pom.xml
index 40e66a2a5..a307844ef 100644
--- a/examples/storm-hdfs-examples/pom.xml
+++ b/examples/storm-hdfs-examples/pom.xml
@@ -93,6 +93,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/examples/storm-jdbc-examples/pom.xml
b/examples/storm-jdbc-examples/pom.xml
index 95525a3cc..5a3e7ab82 100644
--- a/examples/storm-jdbc-examples/pom.xml
+++ b/examples/storm-jdbc-examples/pom.xml
@@ -85,6 +85,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/examples/storm-jms-examples/pom.xml
b/examples/storm-jms-examples/pom.xml
index 41cf4b9bd..47f765b7f 100644
--- a/examples/storm-jms-examples/pom.xml
+++ b/examples/storm-jms-examples/pom.xml
@@ -105,6 +105,16 @@
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/examples/storm-kafka-client-examples/pom.xml
b/examples/storm-kafka-client-examples/pom.xml
index 4700be771..f29819951 100644
--- a/examples/storm-kafka-client-examples/pom.xml
+++ b/examples/storm-kafka-client-examples/pom.xml
@@ -103,6 +103,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/examples/storm-loadgen/pom.xml b/examples/storm-loadgen/pom.xml
index 0ae6a3b99..16080974f 100644
--- a/examples/storm-loadgen/pom.xml
+++ b/examples/storm-loadgen/pom.xml
@@ -122,6 +122,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/examples/storm-perf/pom.xml b/examples/storm-perf/pom.xml
index 7e9377cbf..69c435b9e 100644
--- a/examples/storm-perf/pom.xml
+++ b/examples/storm-perf/pom.xml
@@ -86,6 +86,16 @@
<mainClass>${storm.topology}</mainClass>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/examples/storm-redis-examples/pom.xml
b/examples/storm-redis-examples/pom.xml
index 68db938f6..662f5e2a3 100644
--- a/examples/storm-redis-examples/pom.xml
+++ b/examples/storm-redis-examples/pom.xml
@@ -89,6 +89,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/examples/storm-starter/pom.xml b/examples/storm-starter/pom.xml
index f7632397f..b6dc1e66c 100644
--- a/examples/storm-starter/pom.xml
+++ b/examples/storm-starter/pom.xml
@@ -174,6 +174,16 @@
<excludedGroups>none</excludedGroups>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-autocreds/pom.xml b/external/storm-autocreds/pom.xml
index 80ecdc4f1..bdf1c3cff 100644
--- a/external/storm-autocreds/pom.xml
+++ b/external/storm-autocreds/pom.xml
@@ -112,6 +112,16 @@
</dependencies>
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-blobstore-migration/pom.xml
b/external/storm-blobstore-migration/pom.xml
index ae3bfa3b8..9150aa0a2 100644
--- a/external/storm-blobstore-migration/pom.xml
+++ b/external/storm-blobstore-migration/pom.xml
@@ -146,6 +146,16 @@ limitations under the License.
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-hdfs-blobstore/pom.xml
b/external/storm-hdfs-blobstore/pom.xml
index a33bbca96..fe31d7fab 100644
--- a/external/storm-hdfs-blobstore/pom.xml
+++ b/external/storm-hdfs-blobstore/pom.xml
@@ -153,6 +153,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-hdfs-oci/pom.xml b/external/storm-hdfs-oci/pom.xml
index 73b3f0c95..e0d4990a9 100644
--- a/external/storm-hdfs-oci/pom.xml
+++ b/external/storm-hdfs-oci/pom.xml
@@ -104,6 +104,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-hdfs/pom.xml b/external/storm-hdfs/pom.xml
index ca1207cfb..565ddd0e6 100644
--- a/external/storm-hdfs/pom.xml
+++ b/external/storm-hdfs/pom.xml
@@ -145,6 +145,16 @@
<forkCount>1</forkCount>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-jdbc/pom.xml b/external/storm-jdbc/pom.xml
index 770754154..4816832ef 100644
--- a/external/storm-jdbc/pom.xml
+++ b/external/storm-jdbc/pom.xml
@@ -77,6 +77,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-jms/pom.xml b/external/storm-jms/pom.xml
index 5aa3fc2ab..dfd61417e 100644
--- a/external/storm-jms/pom.xml
+++ b/external/storm-jms/pom.xml
@@ -80,6 +80,16 @@
<additionalparam>-Xdoclint:none</additionalparam>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-kafka-client/pom.xml
b/external/storm-kafka-client/pom.xml
index 368c3dce5..b47b75e3a 100644
--- a/external/storm-kafka-client/pom.xml
+++ b/external/storm-kafka-client/pom.xml
@@ -173,6 +173,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-kafka-migration/pom.xml
b/external/storm-kafka-migration/pom.xml
index 1a0f1e580..f2bcde4ba 100644
--- a/external/storm-kafka-migration/pom.xml
+++ b/external/storm-kafka-migration/pom.xml
@@ -81,6 +81,16 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-kafka-monitor/pom.xml
b/external/storm-kafka-monitor/pom.xml
index 326e00a34..0aa34b473 100644
--- a/external/storm-kafka-monitor/pom.xml
+++ b/external/storm-kafka-monitor/pom.xml
@@ -81,6 +81,16 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-metrics/pom.xml b/external/storm-metrics/pom.xml
index 31197865b..da14dbce2 100644
--- a/external/storm-metrics/pom.xml
+++ b/external/storm-metrics/pom.xml
@@ -99,6 +99,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/external/storm-redis/pom.xml b/external/storm-redis/pom.xml
index 0c240cea9..9a380b24b 100644
--- a/external/storm-redis/pom.xml
+++ b/external/storm-redis/pom.xml
@@ -100,6 +100,16 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/flux/flux-core/pom.xml b/flux/flux-core/pom.xml
index 3f5b9957a..2fcb43fa4 100644
--- a/flux/flux-core/pom.xml
+++ b/flux/flux-core/pom.xml
@@ -114,6 +114,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/flux/flux-examples/pom.xml b/flux/flux-examples/pom.xml
index 6cfaa7e3f..bc3746189 100644
--- a/flux/flux-examples/pom.xml
+++ b/flux/flux-examples/pom.xml
@@ -121,6 +121,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/flux/flux-wrappers/pom.xml b/flux/flux-wrappers/pom.xml
index 0b69c5672..635daa788 100644
--- a/flux/flux-wrappers/pom.xml
+++ b/flux/flux-wrappers/pom.xml
@@ -52,6 +52,16 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/flux/pom.xml b/flux/pom.xml
index fc1712573..098290f12 100644
--- a/flux/pom.xml
+++ b/flux/pom.xml
@@ -60,6 +60,16 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/integration-test/pom.xml b/integration-test/pom.xml
index f9295f1e7..922a6332f 100644
--- a/integration-test/pom.xml
+++ b/integration-test/pom.xml
@@ -163,6 +163,16 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/pom.xml b/pom.xml
index 4bb39882c..7fc37e9e4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,6 +69,15 @@
<properties>
<javaVersion>25</javaVersion>
+ <!-- checkstyle auto-fix tooling (run on demand, see
storm-checkstyle/README.md) -->
+ <rewrite-maven-plugin.version>6.46.1</rewrite-maven-plugin.version>
+
<rewrite-static-analysis.version>2.41.1</rewrite-static-analysis.version>
+ <gmavenplus.version>5.1.0</gmavenplus.version>
+ <groovy.version>4.0.33</groovy.version>
+ <!-- Used by both the checkstyle check and checkstyle-autofix.groovy.
If you change this, you should
+ also update the storm_checkstyle.xml file to be based on the
google_checks.xml from the version
+ of checkstyle you are choosing. -->
+ <checkstyle.version>14.3.0</checkstyle.version>
<maven.javadoc.failOnError>false</maven.javadoc.failOnError>
<maven.javadoc.failOnWarnings>false</maven.javadoc.failOnWarnings>
@@ -1113,6 +1122,137 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <!--
+ OpenRewrite stages of the checkstyle auto-fix (see
storm-checkstyle/README.md). Not bound to
+ any phase: run on demand. The recipes and the style
mirroring storm_checkstyle.xml are in
+ rewrite.yml at the repo root. Thrift-generated code and
the Groovy scripts of
+ storm-checkstyle are not checked by Checkstyle, so they
are left alone.
+ -->
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <version>${rewrite-maven-plugin.version}</version>
+ <configuration>
+
<configLocation>${maven.multiModuleProjectDirectory}/rewrite.yml</configLocation>
+ <activeStyles>
+ <style>org.apache.storm.checkstyle.Style</style>
+ </activeStyles>
+ <exclusions>
+ <exclusion>**/generated/**</exclusion>
+ <exclusion>**/resources/storm/*.groovy</exclusion>
+ </exclusions>
+ <!-- Styles derived from the ruleset conflict with it
(RightCurly 'alone' would push
+ catch/else onto their own line); the explicit style in
rewrite.yml is used instead. -->
+
<checkstyleDetectionEnabled>false</checkstyleDetectionEnabled>
+ <exportDatatables>false</exportDatatables>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>org.openrewrite.recipe</groupId>
+ <artifactId>rewrite-static-analysis</artifactId>
+
<version>${rewrite-static-analysis.version}</version>
+ </dependency>
+ </dependencies>
+ <executions>
+ <execution>
+ <!-- first pass, before checkstyle-autofix.groovy
-->
+ <id>checkstyle-autofix-openrewrite</id>
+ <phase>none</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <activeRecipes>
+
<recipe>org.apache.storm.checkstyle.AutoFix</recipe>
+ </activeRecipes>
+ </configuration>
+ </execution>
+ <execution>
+ <!-- re-indent pass, after
checkstyle-autofix.groovy -->
+ <id>checkstyle-reindent-openrewrite</id>
+ <phase>none</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <activeRecipes>
+
<recipe>org.apache.storm.checkstyle.Reindent</recipe>
+ </activeRecipes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <!--
+ Groovy stages of the checkstyle auto-fix and license
header normalization (see
+ storm-checkstyle/README.md). Not bound to any phase: run
on demand. gmavenplus interprets
+ the scripts directly, so they need no prior `compile`
phase.
+ -->
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <version>${gmavenplus.version}</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ <version>${groovy.version}</version>
+ </dependency>
+ <dependency>
+ <!-- XmlSlurper, used by checkstyle-autofix.groovy
to parse the
+ checkstyle ruleset itself - split out of Groovy
core since Groovy 4. -->
+ <groupId>org.apache.groovy</groupId>
+ <artifactId>groovy-xml</artifactId>
+ <version>${groovy.version}</version>
+ </dependency>
+ <dependency>
+ <!-- checkstyle-autofix.groovy runs Checkstyle
itself to find what to fix, so it
+ must be the same version the checkstyle check
uses. -->
+ <groupId>com.puppycrawl.tools</groupId>
+ <artifactId>checkstyle</artifactId>
+ <version>${checkstyle.version}</version>
+ <exclusions>
+ <!-- only needed for Checkstyle's site/XDoc
output, not to run the checker -->
+ <exclusion>
+ <groupId>org.apache.maven.doxia</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <!-- needed at run time by Checkstyle
(commons-beanutils) -->
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>${commons-logging.version}</version>
+ </dependency>
+ </dependencies>
+ <executions>
+ <execution>
+ <id>checkstyle-autofix</id>
+ <phase>none</phase>
+ <goals>
+ <goal>execute</goal>
+ </goals>
+ <configuration>
+ <scripts>
+
<script>file:///${maven.multiModuleProjectDirectory}/storm-checkstyle/src/main/resources/storm/checkstyle-autofix.groovy</script>
+ </scripts>
+ </configuration>
+ </execution>
+ <execution>
+ <!-- add -Dlicense.check=true to only report -->
+ <id>normalize-license-headers</id>
+ <phase>none</phase>
+ <goals>
+ <goal>execute</goal>
+ </goals>
+ <configuration>
+ <scripts>
+
<script>file:///${maven.multiModuleProjectDirectory}/storm-checkstyle/src/main/resources/storm/normalize-license-headers.groovy</script>
+ </scripts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<!--To support checkstyle goals. For example: "mvn
checkstyle:checkstyle"-->
<groupId>org.apache.maven.plugins</groupId>
@@ -1127,9 +1267,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
- <!-- If you change this, you should also update
the storm_checkstyle.xml file to be
- based on the google_checks.xml from the version of
checkstyle you are choosing. -->
- <version>14.3.0</version>
+ <version>${checkstyle.version}</version>
</dependency>
</dependencies>
<executions>
diff --git a/rewrite.yml b/rewrite.yml
new file mode 100644
index 000000000..727881b27
--- /dev/null
+++ b/rewrite.yml
@@ -0,0 +1,202 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+---
+# First stage of the checkstyle auto-fix, run on demand by the
rewrite-maven-plugin
+# execution `checkstyle-autofix-openrewrite` in the root pom (bound to no
phase), just before
+# checkstyle-autofix.groovy (which then fixes what is left, text-level, driven
by
+# Checkstyle's own reports).
+#
+# Everything OpenRewrite has a recipe for lives here; the Groovy stage keeps
only the rules
+# with no recipe (see storm-checkstyle/README.md for the split). The
formatting recipes need
+# a style, which is declared below (org.apache.storm.checkstyle.Style) and
mirrors
+# storm_checkstyle.xml instead of being derived from it: the derived styles
conflict with the
+# ruleset (RightCurly 'alone' would push catch/else onto their own line).
+#
+# Deliberately NOT here:
+# - org.openrewrite.java.format.AutoFormat: bundles formatters that do not
follow the ruleset
+# and introduces star imports (AvoidStarImport).
+# - org.openrewrite.staticanalysis.TypecastParenPad: turns `(T) x` into
`(T)x`, which
+# violates WhitespaceAfter (the Spaces recipe below covers ParenPad on its
own).
+type: specs.openrewrite.org/v1beta/recipe
+name: org.apache.storm.checkstyle.AutoFix
+displayName: Fix storm_checkstyle.xml violations that need a parsed AST
+description: >
+ NeedBraces, MultipleVariableDeclarations, ModifierOrder, UpperEll,
ArrayTypeStyle,
+ FileTabCharacter, OperatorWrap, WhitespaceAround/After, NoWhitespaceBefore,
ParenPad,
+ MethodParamPad, GenericWhitespace, Indentation, EmptyLineSeparator,
CustomImportOrder,
+ AvoidStarImport and MissingSwitchDefault (only a misplaced default).
+recipeList:
+ - org.openrewrite.staticanalysis.NeedBraces
+ - org.openrewrite.staticanalysis.MultipleVariableDeclarations
+ - org.openrewrite.staticanalysis.ModifierOrder
+ - org.openrewrite.staticanalysis.UpperCaseLiteralSuffixes
+ - org.openrewrite.staticanalysis.UseJavaStyleArrayDeclarations
+ # OperatorWrap (option NL in the ruleset)
+ - org.openrewrite.staticanalysis.OperatorWrap:
+ wrapOption: NL
+ # FileTabCharacter
+ - org.openrewrite.java.format.NormalizeTabsOrSpaces
+ # WhitespaceAround, WhitespaceAfter, NoWhitespaceBefore, ParenPad,
MethodParamPad, GenericWhitespace
+ - org.openrewrite.java.format.Spaces
+ # LeftCurly, RightCurly, NoLineWrap, AnnotationLocation (partial: see
storm-checkstyle/README.md)
+ - org.openrewrite.java.format.WrappingAndBraces
+ # Indentation, CommentsIndentation
+ - org.openrewrite.java.format.TabsAndIndents
+ # EmptyLineSeparator
+ - org.openrewrite.java.format.BlankLines
+ # AvoidStarImport: unfolds star imports to the types actually used (needs
type attribution)
+ - org.openrewrite.java.RemoveUnusedImports
+ # MissingSwitchDefault: only moves an existing `default` last
+ - org.openrewrite.staticanalysis.DefaultComesLast
+ # CustomImportOrder (STATIC###THIRD_PARTY_PACKAGE, sorted, one blank line
between the groups)
+ - org.openrewrite.java.OrderImports:
+ removeUnused: false
+---
+# Second pass, run after checkstyle-autofix.groovy: its LineLength wraps leave
continuation lines
+# at the wrong indent. Only TabsAndIndents runs here; Spaces would turn the
`{}` the Groovy stage
+# writes for empty blocks back into `{ }`.
+type: specs.openrewrite.org/v1beta/recipe
+name: org.apache.storm.checkstyle.Reindent
+displayName: Re-indent after the Groovy stage
+description: Indentation only.
+recipeList:
+ - org.openrewrite.java.format.TabsAndIndents
+---
+type: specs.openrewrite.org/v1beta/style
+name: org.apache.storm.checkstyle.Style
+displayName: Style mirroring storm_checkstyle.xml
+styleConfigs:
+ - org.openrewrite.java.style.TabsAndIndentsStyle:
+ useTabCharacter: false
+ tabSize: 4
+ indentSize: 4
+ continuationIndent: 8
+ indentsRelativeToExpressionStart: false
+ - org.openrewrite.java.style.BlankLinesStyle:
+ keepMaximum:
+ inDeclarations: 1
+ inCode: 1
+ beforeEndOfBlock: 1
+ betweenHeaderAndPackage: 1
+ minimum:
+ beforePackage: 0
+ afterPackage: 1
+ beforeImports: 1
+ afterImports: 1
+ aroundClass: 1
+ afterClassHeader: 0
+ beforeClassEnd: 0
+ afterAnonymousClassHeader: 0
+ aroundFieldInInterface: 0
+ aroundField: 0
+ aroundMethodInInterface: 1
+ aroundMethod: 1
+ beforeMethodBody: 0
+ aroundInitializer: 1
+ - org.openrewrite.java.style.ImportLayoutStyle:
+ # static imports first, then everything else; never fold into a star
import
+ classCountToUseStarImport: 2147483647
+ nameCountToUseStarImport: 2147483647
+ layout:
+ - import static all other imports
+ - <blank line>
+ - import all other imports
+ - org.openrewrite.java.style.WrappingAndBracesStyle:
+ # RightCurly 'same' for else/catch/finally; annotations of types and
methods on their own line
+ ifStatement:
+ elseOnNewLine: false
+ specialElseIfTreatment: true
+ tryStatement:
+ catchOnNewLine: false
+ finallyOnNewLine: false
+ classAnnotations:
+ wrap: WrapAlways
+ methodAnnotations:
+ wrap: WrapAlways
+ - org.openrewrite.java.style.SpacesStyle:
+ beforeParentheses:
+ methodDeclaration: false
+ methodCall: false
+ ifParentheses: true
+ forParentheses: true
+ whileParentheses: true
+ switchParentheses: true
+ tryParentheses: true
+ catchParentheses: true
+ synchronizedParentheses: true
+ annotationParameters: false
+ aroundOperators:
+ assignment: true
+ logical: true
+ equality: true
+ relational: true
+ bitwise: true
+ additive: true
+ multiplicative: true
+ shift: true
+ unary: false
+ lambdaArrow: true
+ methodReferenceDoubleColon: false
+ beforeLeftBrace:
+ classLeftBrace: true
+ methodLeftBrace: true
+ ifLeftBrace: true
+ elseLeftBrace: true
+ forLeftBrace: true
+ whileLeftBrace: true
+ doLeftBrace: true
+ switchLeftBrace: true
+ tryLeftBrace: true
+ catchLeftBrace: true
+ finallyLeftBrace: true
+ synchronizedLeftBrace: true
+ arrayInitializerLeftBrace: true
+ annotationArrayInitializerLeftBrace: false
+ within:
+ codeBraces: true
+ brackets: false
+ arrayInitializerBraces: false
+ emptyArrayInitializerBraces: false
+ groupingParentheses: false
+ methodDeclarationParentheses: false
+ emptyMethodDeclarationParentheses: false
+ methodCallParentheses: false
+ emptyMethodCallParentheses: false
+ ifParentheses: false
+ forParentheses: false
+ whileParentheses: false
+ switchParentheses: false
+ tryParentheses: false
+ catchParentheses: false
+ synchronizedParentheses: false
+ typeCastParentheses: false
+ annotationParentheses: false
+ angleBrackets: false
+ recordHeader: false
+ other:
+ beforeComma: false
+ afterComma: true
+ beforeForSemicolon: false
+ afterForSemicolon: true
+ afterTypeCast: true
+ beforeColonInForEach: true
+ insideOneLineEnumBraces: false
+ aroundEqualInAnnotationValuePair: true
+ typeArguments:
+ afterComma: true
+ beforeOpeningAngleBracket: false
+ afterClosingAngleBracket: false
diff --git a/storm-checkstyle/README.md b/storm-checkstyle/README.md
new file mode 100644
index 000000000..630718545
--- /dev/null
+++ b/storm-checkstyle/README.md
@@ -0,0 +1,144 @@
+# Checkstyle auto-fix
+
+`storm_checkstyle.xml` is the Checkstyle ruleset enforced on every `mvn`
build.
+Many rules are `severity=warning` as they do not fail the build, but they
should still be
+slimmed to the minimal amount
+
+The auto-fix is **not** part of the normal build. It is run on demand in two
stages,
+in every module that declares the plugins:
+
+1. **`rewrite-maven-plugin` (OpenRewrite)** - reduced amount of fixes done on
a parsed Java AST.
+ The recipe list is `rewrite.yml` at the repo root
(`org.apache.storm.checkstyle.AutoFix`).
+2. **`src/main/resources/storm/checkstyle-autofix.groovy`** (via
`gmavenplus-plugin`) -
+ runs Checkstyle itself, in-process, with the same version and ruleset the
build
+ uses, reads violations (file, line, column, message) and hands each one to
a small fixer written for the one
+ Checkstyle module that reported it. The cycle until no more rules can be
fixed.
+
+The values a fixer needs (`Indentation` `basicOffset`, `LineLength` `max`,
+`CustomImportOrder` rules) are read from the ruleset XML at run time avoiding
hardcoding. Files that the
+module excludes from Checkstyle (`<excludes>` of `maven-checkstyle-plugin`,
e.g. thrift
+generated code) are neither audited nor rewritten.
+
+The license comment at the top of a file is never touched, whatever Checkstyle
reports in it
+(`LineLength` on a wide-wrapped header, `JavadocParagraph` on one opened with
`/**`): both
+wrappings of the ASF header are accepted, see [License
headers](#license-headers).
+
+Safety net, per file: a file that stops parsing after a fix is restored to the
state
+before that round; a file that ends with more violations than it started with
is
+restored to its original text.
+
+## What it fixes
+
+| Checkstyle module | Stage |
+|---|---|
+| `NeedBraces`, `MultipleVariableDeclarations`, `ModifierOrder`, `UpperEll`,
`ArrayTypeStyle`, `FileTabCharacter`, `OperatorWrap` | OpenRewrite (dedicated
recipes) |
+| `WhitespaceAround`, `WhitespaceAfter`, `NoWhitespaceBefore`, `ParenPad`,
`MethodParamPad`, `GenericWhitespace` | OpenRewrite (`Spaces`, style
`org.apache.storm.checkstyle.Style`) |
+| `Indentation`, `EmptyLineSeparator`, `CustomImportOrder` | OpenRewrite
(`TabsAndIndents`, `BlankLines`, `OrderImports`, same style) |
+| `AvoidStarImport` | OpenRewrite (`RemoveUnusedImports`; needs type
attribution, i.e. the module's dependencies resolvable) |
+| `MissingSwitchDefault` | OpenRewrite (`DefaultComesLast`: only moves an
existing `default` last, it does not add a missing one) |
+| `LeftCurly`, `RightCurly`, `NoLineWrap`, `AnnotationLocation` | OpenRewrite
(`WrappingAndBraces`), partial: it does not move `{`, `catch`/`finally` or
every annotation; what is left is not chased in Groovy |
+| `NoWhitespaceBeforeCaseDefaultColon`, `RegexpSinglelineJava` (empty-block
spacing) | OpenRewrite (`Spaces`), partial: it writes `{ }` for empty blocks,
which `RegexpSinglelineJava` rejects |
+| `CommentsIndentation` | OpenRewrite (`TabsAndIndents`), partial |
+| `OneStatementPerLine`, `SeparatorWrap` | Groovy (no recipe) |
+| `LineLength` | Groovy: re-wraps comments and Javadoc, and breaks code at
commas, `&&`/`\|\|`/`+`/`?`, `.method(` chains and `=`, and splits long string
literals into `"a" + "b"` |
+| `IllegalTokenText`, `AvoidEscapedUnicodeCharacters`, `TodoComment`,
single-line comment space | Groovy |
+| `OverloadMethodsDeclarationOrder`, `ConstructorsDeclarationGrouping` |
Groovy: the member is moved next to its overloads/constructors |
+| `JavadocLeadingAsteriskAlign`, `JavadocMissingLeadingAsterisk`,
`JavadocContentLocation`, `JavadocParagraph`,
`JavadocTagContinuationIndentation`, `RequireEmptyLineBeforeBlockTagGroup`,
`AtclauseOrder`, `InvalidJavadocPosition`, `SummaryJavadoc` (missing period,
lowercase first word) | Groovy |
+
+The OpenRewrite style mirrors the ruleset by hand (`rewrite.yml`); keep
`continuationIndent`
+at 8, the convention of the code base (Checkstyle's `lineWrappingIndentation`
of 4 is a minimum).
+
+## License headers
+
+`storm-checkstyle/src/main/resources/storm/normalize-license-headers.groovy`
rewrites the
+leading license comment of the module's Java, Groovy, XML, Markdown, YAML,
properties, Python and
+shell files to the exact template of
+https://www.apache.org/legal/src-headers.html#headers, but only when its
wording is wrong (a typo,
+missing words). A header with the words of the template is left alone whatever
its line wrapping,
+its case (the older `to You` form) or the Javadoc `<p>` an IDE formatter
added: rewrapping it would
+touch most files of the code base and pollute `git blame` for no legal gain.
Only the license lines
+are replaced: `#` comment lines around them (a `-*- coding -*-` line, a note
on the script's
+arguments) are kept, and a `/* */` or `<!-- -->` comment with anything besides
the license is left
+alone. A file whose leading comment is not an ASF license header, or that
carries a copyright
+notice of anybody but the ASF (a third-party work), is left alone too:
+
+```sh
+mvn -pl <module>
org.codehaus.gmavenplus:gmavenplus-plugin:execute@normalize-license-headers
+```
+
+Add `-Dlicense.check=true` to only report (the build fails if a file would
change).
+
+## Not fixed
+
+The following violations can't be fixed mechanically in a satisfactory way:
+
+- `MissingJavadocMethod`, `MissingJavadocType`, `JavadocMethod`,
`NonEmptyAtclauseDescription`,
+ `SingleLineJavadoc`, the rest of `SummaryJavadoc` - human intervention for
docs.
+- `MethodName`, the other naming rules and `AbbreviationAsWordInName` - a
rename needs
+ whole-repo symbol resolution.
+- `FallThrough`, `EmptyCatchBlock` - comments could silence bugs.
+- `VariableDeclarationUsageDistance` - moving a declaration can reorder side
effects.
+- `OneTopLevelClass`, `OuterTypeFilename`, `LeftCurly` on `case X: {`,
+ `TextBlockGoogleStyleFormatting`.
+- `LineLength` on lines with no safe break point (long unbreakable tokens,
URLs).
+
+### Partial OpenRewrite coverage
+
+`WrappingAndBraces` (custom `WrappingAndBracesStyle`) does not move `{` to the
end of the line and
+leaves `catch`/`finally` and some annotations where they are; human
intervention is needed.
+
+### Rejected OpenRewrite recipes
+
+`org.openrewrite.java.format.AutoFormat` (bundles formatters that ignore the
ruleset and
+introduces star imports) and `TypecastParenPad` (turns `(T) x` into `(T)x`,
violating
+`WhitespaceAfter`) are not used.
+
+## Running it
+
+Three passes: OpenRewrite, the Groovy stage, then the OpenRewrite re-indent
pass (the Groovy
+`LineLength` wraps leave continuation lines mis-indented; the pass runs
`TabsAndIndents` only, since
+`Spaces` would turn the `{}` of empty blocks back into `{ }`). Review the
result with `git diff`:
+
+```sh
+mvn
org.openrewrite.maven:rewrite-maven-plugin:run@checkstyle-autofix-openrewrite \
+ org.codehaus.gmavenplus:gmavenplus-plugin:execute@checkstyle-autofix \
+
org.openrewrite.maven:rewrite-maven-plugin:run@checkstyle-reindent-openrewrite \
+ -pl '!storm-shaded-deps' -Dcheckstyle.skip=true
+```
+
+`-Dcheckstyle.skip=true` is needed: `rewrite:run` forks the lifecycle up to
+`process-test-classes`, which would otherwise run the `validate`-bound
checkstyle check
+and block the auto-fix on the very violations it is meant to fix. The Groovy
stage runs
+Checkstyle itself and is unaffected by the flag.
+
+For a single module, replace the `-pl` argument (e.g. `-pl storm-client`); the
Groovy
+stage may need a second invocation of the same command if you want to be sure
nothing
+is left, since each run already repeats until nothing more can be fixed. To
confirm
+the result against the build's own rules afterwards:
+
+```sh
+mvn validate -pl '!storm-shaded-deps'
+```
+
+### From GitHub Actions
+
+The `Checkstyle auto-fix` workflow
(`.github/workflows/checkstyle-autofix.yaml`, run by hand from
+the Actions tab) runs the three passes and the license header normalization on
one module. It
+pushes the result to a new branch `checkstyle-fix/<module>-<run id>-<attempt>`
and opens a pull
+request from it against the branch the workflow ran on. The build's Checkstyle
check runs before
+and after the fix: the counts, and the violations left per check, are in the
pull request and the
+run summary, and both full reports are in the run's `checkstyle-reports`
artifact. The module must
+declare `rewrite-maven-plugin` and `gmavenplus-plugin` (directly or through
its parent), otherwise
+the job fails instead of silently fixing nothing.
+
+### Reviewing what it did
+
+Both stages edit source files in place, so `git diff` shows exactly what
changed.
+
+### Upgrading Checkstyle
+
+`checkstyle.version` (root `pom.xml`) is used by both the Checkstyle check and
the
+Groovy stage, so they can't disagree. If you change it, also update
+`storm_checkstyle.xml` to match the new `google_checks.xml`: the Groovy fixers
parse
+Checkstyle's message text, and a reworded message makes that fixer skip the
violation
+(nothing breaks - Checkstyle still reports it).
diff --git
a/storm-checkstyle/src/main/resources/storm/checkstyle-autofix.groovy
b/storm-checkstyle/src/main/resources/storm/checkstyle-autofix.groovy
new file mode 100644
index 000000000..295a01155
--- /dev/null
+++ b/storm-checkstyle/src/main/resources/storm/checkstyle-autofix.groovy
@@ -0,0 +1,1633 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Auto-fixes storm_checkstyle.xml violations.
+ *
+ * Script runs Checkstyle itself (in-process, same version and same ruleset
the build's checkstyle check uses) and
+ * reads back the exact violations - file, line, column, message.
+ * Each violation is handed to a small fixer written for the one
+ * Checkstyle module that reported it, which edits only the text that
violation points at.
+ * Because the fixers act on Checkstyle's own reports, they never touch code
Checkstyle is
+ * happy with, and there is no formatter with a style of its own to drift from
the
+ * ruleset. The audit/fix cycle repeats (a fix can unmask or move another
violation)
+ * until nothing more can be fixed. Values a fixer needs (Indentation's
basicOffset,
+ * LineLength's max, CustomImportOrder's rule string) are read straight from
the ruleset
+ * XML; a fixer whose parameters aren't declared there is skipped rather than
guessing.
+ *
+ * The license comment at the top of a file is never touched, whatever
Checkstyle reports in it
+ * (LineLength on a wide-wrapped header, JavadocParagraph on one opened with
'/**'): both
+ * wrappings of the ASF header are accepted (see
normalize-license-headers.groovy), and
+ * re-laying them out would touch most files of the code base for no gain.
+ *
+ * Safety net, per file: a file that stops parsing after a fix is restored to
its state
+ * before the last round and left alone; a file that ends up with more
violations than it
+ * started with is restored to its original text.
+ *
+ * Only the rules OpenRewrite has no recipe for are fixed here (everything
OpenRewrite can do,
+ * fully or partly, runs in its stage, see rewrite.yml: the leftovers are not
chased here).
+ * Fixers (by Checkstyle module):
+ * Layout OneStatementPerLine, LineLength (comments,
argument/operator/call-chain wraps,
+ * long string literals)
+ * Wrapping SeparatorWrap
+ * Ordering OverloadMethodsDeclarationOrder, ConstructorsDeclarationGrouping
+ * Style IllegalTokenText, AvoidEscapedUnicodeCharacters, TodoComment,
+ * single-line-comment-space (MatchXpath)
+ * Javadoc JavadocLeadingAsteriskAlign, JavadocMissingLeadingAsterisk,
+ * JavadocContentLocation, JavadocParagraph,
JavadocTagContinuationIndentation,
+ * RequireEmptyLineBeforeBlockTagGroup, AtclauseOrder,
SummaryJavadoc
+ * (missing period, lowercase first word), InvalidJavadocPosition
+ *
+ * Deliberately not fixed - each needs a decision or knowledge a text edit
can't have:
+ * Naming rules and AbbreviationAsWordInName (renames need cross-file symbol
resolution),
+ * the MissingJavadoc checks, JavadocMethod and
+ * NonEmptyAtclauseDescription (need real prose), FallThrough and
EmptyCatchBlock (a
+ * comment would only silence a possible bug),
VariableDeclarationUsageDistance (moving a
+ * declaration can reorder side effects),
OneTopLevelClass/OuterTypeFilename, LeftCurly on
+ * `case X: {`, TextBlockGoogleStyleFormatting. Checkstyle keeps checking
all of these.
+ *
+ * Run on demand via gmavenplus-plugin's `execute` goal (execution id
`checkstyle-autofix`,
+ * bound to no phase, so a normal build never rewrites sources); see
storm-checkstyle/README.md
+ * for the command. Declared per-module (root pom pluginManagement) since
+ * project.compileSourceRoots / testCompileSourceRoots are module-specific.
+ */
+
+import com.puppycrawl.tools.checkstyle.Checker
+import com.puppycrawl.tools.checkstyle.ConfigurationLoader
+import com.puppycrawl.tools.checkstyle.PropertiesExpander
+import com.puppycrawl.tools.checkstyle.api.AuditEvent
+import com.puppycrawl.tools.checkstyle.api.AuditListener
+import groovy.io.FileType
+import groovy.transform.CompileStatic
+import groovy.xml.XmlSlurper
+
+import java.util.regex.Matcher
+
+//
=================================================================================
+// Src: a mutable source text plus the scanning helpers the fixers share.
Everything is
+// expressed in character offsets into the *current* text. `mask` classifies
each char as
+// code / comment / string-or-char-literal, so scans never mistake the
contents of a
+// string or comment for syntax.
+//
=================================================================================
+
+@CompileStatic
+class Src {
+
+static final byte CODE = 0
+
+static final byte COMMENT = 1
+
+static final byte LITERAL = 2
+
+
+private final StringBuilder sb
+
+private byte[] maskCache
+
+ Src(String text) {
+ this.sb = new StringBuilder(text)
+ }
+
+ @Override
+ String toString() {
+ sb.toString()
+ }
+
+ int length() {
+ sb.length()
+ }
+
+ char charAt(int i) {
+ sb.charAt(i)
+ }
+
+ String slice(int from, int to) {
+ sb.substring(from, to)
+ }
+
+ int indexOf(String s, int from) {
+ sb.indexOf(s, from)
+ }
+
+ int lastIndexOf(String s, int from) {
+ sb.lastIndexOf(s, from)
+ }
+
+ boolean has(int i, String s) {
+ if (i < 0 || i + s.length() > sb.length()) {
+ return false
+ }
+ for (int k = 0; k < s.length(); k++) {
+ if (sb.charAt(i + k) != s.charAt(k)) {
+ return false
+ }
+ }
+ true
+ }
+
+ void replace(int from, int to, String s) {
+ sb.replace(from, to, s)
+ maskCache = null
+ }
+
+ void insert(int at, String s) {
+ sb.insert(at, s)
+ maskCache = null
+ }
+
+ void delete(int from, int to) {
+ sb.delete(from, to)
+ maskCache = null
+ }
+
+ // ---- lines
------------------------------------------------------------------
+
+ int lineStart(int off) {
+ off <= 0 ? 0 : sb.lastIndexOf('\n', off - 1) + 1
+ }
+
+ int lineEnd(int off) {
+ int i = sb.indexOf('\n', off)
+ i < 0 ? sb.length() : i
+ }
+
+ String line(int off) {
+ slice(lineStart(off), lineEnd(off))
+ }
+
+ boolean isHorizontalWs(int i) {
+ char c = sb.charAt(i)
+ c == (char) ' ' || c == (char) '\t'
+ }
+
+ /** Index of the first non-blank char on the line that starts at ls (or
the line end). */
+ int firstNonWs(int ls) {
+ int i = ls
+ while (i < sb.length() && isHorizontalWs(i)) {
+ i++
+ }
+ i
+ }
+
+ String indentOf(int off) {
+ int ls = lineStart(off)
+ slice(ls, firstNonWs(ls))
+ }
+
+ boolean isBlankLine(int off) {
+ line(off).trim().isEmpty()
+ }
+
+ /** True if the line holds only comment text (and whitespace). */
+ boolean isCommentOnlyLine(int off) {
+ int ls = lineStart(off)
+ int le = lineEnd(off)
+ boolean seen = false
+ for (int i = ls; i < le; i++) {
+ if (Character.isWhitespace(sb.charAt(i))) {
+ continue
+ }
+ if (mask()[i] != COMMENT) {
+ return false
+ }
+ seen = true
+ }
+ seen
+ }
+
+ // ---- code/comment/literal classification
------------------------------------
+
+ byte[] mask() {
+ if (maskCache == null) {
+ maskCache = computeMask(sb)
+ }
+ maskCache
+ }
+
+ boolean isCode(int i) {
+ i >= 0 && i < sb.length() && mask()[i] == CODE
+ }
+
+ boolean isComment(int i) {
+ i >= 0 && i < sb.length() && mask()[i] == COMMENT
+ }
+
+ boolean isLiteral(int i) {
+ i >= 0 && i < sb.length() && mask()[i] == LITERAL
+ }
+
+ boolean isCodeChar(int i) {
+ isCode(i) && !Character.isWhitespace(sb.charAt(i))
+ }
+
+ /** Last code (non-whitespace, non-comment, non-literal-interior is NOT
excluded) char at or before i. */
+ int prevCode(int i) {
+ byte[] m = mask()
+ for (int j = Math.min(i, sb.length() - 1); j >= 0; j--) {
+ if (m[j] != COMMENT && !Character.isWhitespace(sb.charAt(j))) {
+ return j
+ }
+ }
+ -1
+ }
+
+ /** First code char at or after i (literals count: their opening quote is
returned). */
+ int nextCode(int i) {
+ byte[] m = mask()
+ for (int j = Math.max(i, 0); j < sb.length(); j++) {
+ if (m[j] != COMMENT && !Character.isWhitespace(sb.charAt(j))) {
+ return j
+ }
+ }
+ -1
+ }
+
+ /** Index of the bracket closing the one at open, ignoring comments and
literals; -1 if none. */
+ int matchClose(int open) {
+ char o = sb.charAt(open)
+ char c = o == (char) '(' ? (char) ')' : (o == (char) '[' ? (char) ']'
: (char) '}')
+ byte[] m = mask()
+ int depth = 0
+ for (int i = open; i < sb.length(); i++) {
+ if (m[i] != CODE) {
+ continue
+ }
+ char ch = sb.charAt(i)
+ if (ch == o) {
+ depth++
+ } else if (ch == c) {
+ depth--
+ if (depth == 0) {
+ return i
+ }
+ }
+ }
+ -1
+ }
+
+ /** Index of the bracket opening the one at close; -1 if none. */
+ int matchOpen(int close) {
+ char c = sb.charAt(close)
+ char o = c == (char) ')' ? (char) '(' : (c == (char) ']' ? (char) '['
: (char) '{')
+ byte[] m = mask()
+ int depth = 0
+ for (int i = close; i >= 0; i--) {
+ if (m[i] != CODE) {
+ continue
+ }
+ char ch = sb.charAt(i)
+ if (ch == c) {
+ depth++
+ } else if (ch == o) {
+ depth--
+ if (depth == 0) {
+ return i
+ }
+ }
+ }
+ -1
+ }
+
+ /** The Java identifier/keyword starting at i ('' if none). */
+ String wordAt(int i) {
+ int j = i
+ while (j < sb.length() &&
Character.isJavaIdentifierPart(sb.charAt(j))) {
+ j++
+ }
+ slice(i, j)
+ }
+
+ static byte[] computeMask(CharSequence t) {
+ int n = t.length()
+ byte[] m = new byte[n]
+ int i = 0
+ while (i < n) {
+ char c = t.charAt(i)
+ char d = i + 1 < n ? t.charAt(i + 1) : (char) 0
+ if (c == (char) '/' && d == (char) '/') {
+ int j = i
+ while (j < n && t.charAt(j) != (char) '\n') {
+ m[j++] = COMMENT
+ }
+ i = j
+ } else if (c == (char) '/' && d == (char) '*') {
+ int j = i + 2
+ while (j < n && !(t.charAt(j) == (char) '*' && j + 1 < n &&
t.charAt(j + 1) == (char) '/')) {
+ j++
+ }
+ int end = Math.min(n, j + 2)
+ for (int k = i; k < end; k++) {
+ m[k] = COMMENT
+ }
+ i = end
+ } else if (c == (char) '"' && d == (char) '"' && i + 2 < n &&
t.charAt(i + 2) == (char) '"') {
+ int j = i + 3
+ while (j < n) {
+ char x = t.charAt(j)
+ if (x == (char) '\\') {
+ j += 2
+ } else if (x == (char) '"' && j + 2 < n + 0 && t.charAt(j
+ 1) == (char) '"'
+ && t.charAt(j + 2) == (char) '"') {
+ j += 3
+ break
+ } else {
+ j++
+ }
+ }
+ int end = Math.min(n, j)
+ for (int k = i; k < end; k++) {
+ m[k] = LITERAL
+ }
+ i = end
+ } else if (c == (char) '"' || c == (char) '\'') {
+ int j = i + 1
+ while (j < n && t.charAt(j) != c && t.charAt(j) != (char)
'\n') {
+ j += t.charAt(j) == (char) '\\' ? 2 : 1
+ }
+ int end = Math.min(n, j < n && t.charAt(j) == c ? j + 1 : j)
+ for (int k = i; k < end; k++) {
+ m[k] = LITERAL
+ }
+ i = end
+ } else {
+ i++
+ }
+ }
+ m
+ }
+}
+
+//
=================================================================================
+// Fixers. Every fixer has the signature
+// static int fix(Src s, int off, String msg, int[] origLineStarts)
+// `off` is the offset Checkstyle reported (already translated to the current
text; it is
+// only ever handed to a fixer while everything before the previous edit is
unchanged),
+// `msg` its message. A fixer returns the lowest offset it modified, or NONE
if it changed
+// nothing. Violations are applied last-to-first, and any violation at or
beyond an
+// already-modified offset is deferred to the next audit round.
+//
=================================================================================
+
+class Fixers {
+
+static final int NONE = -1
+
+
+/** Set from the ruleset: Indentation.basicOffset, LineLength.max. */
+static int basicOffset = 4
+
+static int maxLineLength = 100
+
+ // ---- small helpers
----------------------------------------------------------
+
+ static String tokenOf(String msg) {
+ Matcher m = msg =~ /'([^']+)'/
+ m.find() ? m.group(1) : null
+ }
+
+ static boolean hws(char c) {
+ c == ' ' as char || c == '\t' as char
+ }
+
+ static String spaces(int n) {
+ ' ' * Math.max(0, n)
+ }
+
+ static String nl(String indent) {
+ '\n' + indent
+ }
+
+ static String oneIndent() {
+ spaces(basicOffset)
+ }
+
+ /** Start of the run of comment lines that sit directly on top of the
declaration at off. */
+ static int leadingBlockStart(Src s, int off) {
+ int ls = s.lineStart(off)
+ while (ls > 0) {
+ int pls = s.lineStart(ls - 1)
+ if (s.isBlankLine(pls) || !s.isCommentOnlyLine(pls)) {
+ break
+ }
+ ls = pls
+ }
+ ls
+ }
+
+ /** End (exclusive) of the member whose header starts at off: after its
body's '}' or its ';'. */
+ static int memberEnd(Src s, int off) {
+ int n = s.length()
+ int i = off
+ while (i < n) {
+ if (!s.isCode(i)) {
+ i++
+ continue
+ }
+ char c = s.charAt(i)
+ if (c == '(' as char || c == '[' as char) {
+ int e = s.matchClose(i)
+ if (e < 0) {
+ return -1
+ }
+ i = e + 1
+ continue
+ }
+ if (c == ';' as char) {
+ return i + 1
+ }
+ if (c == '{' as char) {
+ int e = s.matchClose(i)
+ return e < 0 ? -1 : e + 1
+ }
+ i++
+ }
+ -1
+ }
+
+ /** End (exclusive) of the statement whose first token is at i; -1 if it
can't be determined. */
+ static int stmtEnd(Src s, int from) {
+ int i = s.nextCode(from)
+ if (i < 0) {
+ return -1
+ }
+ char c = s.charAt(i)
+ if (c == '{' as char) {
+ int e = s.matchClose(i)
+ return e < 0 ? -1 : e + 1
+ }
+ if (c == ';' as char) {
+ return i + 1
+ }
+ String w = s.wordAt(i)
+ switch (w) {
+ case 'if':
+ case 'for':
+ case 'while':
+ int p = s.nextCode(i + w.length())
+ if (p < 0 || s.charAt(p) != '(' as char) {
+ return -1
+ }
+ int close = s.matchClose(p)
+ if (close < 0) {
+ return -1
+ }
+ int body = stmtEnd(s, close + 1)
+ if (w == 'if' && body >= 0) {
+ int nxt = s.nextCode(body)
+ if (nxt >= 0 && s.wordAt(nxt) == 'else') {
+ return stmtEnd(s, nxt + 4)
+ }
+ }
+ return body
+ case 'do':
+ int b = stmtEnd(s, i + 2)
+ if (b < 0) {
+ return -1
+ }
+ int wh = s.nextCode(b)
+ if (wh < 0 || s.wordAt(wh) != 'while') {
+ return -1
+ }
+ int wp = s.nextCode(wh + 5)
+ int wc = wp < 0 ? -1 : s.matchClose(wp)
+ int semi = wc < 0 ? -1 : s.nextCode(wc + 1)
+ return semi >= 0 && s.charAt(semi) == ';' as char ? semi + 1 :
-1
+ case 'try':
+ int tp = s.nextCode(i + 3)
+ if (tp >= 0 && s.charAt(tp) == '(' as char) {
+ int rc = s.matchClose(tp)
+ tp = rc < 0 ? -1 : s.nextCode(rc + 1)
+ }
+ if (tp < 0 || s.charAt(tp) != '{' as char) {
+ return -1
+ }
+ int e = s.matchClose(tp) + 1
+ while (e > 0) {
+ int nxt = s.nextCode(e)
+ String nw = nxt < 0 ? '' : s.wordAt(nxt)
+ if (nw == 'catch') {
+ int cp = s.nextCode(nxt + 5)
+ int cc = cp < 0 ? -1 : s.matchClose(cp)
+ int cb = cc < 0 ? -1 : s.nextCode(cc + 1)
+ if (cb < 0 || s.charAt(cb) != '{' as char) {
+ return -1
+ }
+ e = s.matchClose(cb) + 1
+ } else if (nw == 'finally') {
+ int fb = s.nextCode(nxt + 7)
+ if (fb < 0 || s.charAt(fb) != '{' as char) {
+ return -1
+ }
+ e = s.matchClose(fb) + 1
+ } else {
+ break
+ }
+ }
+ return e > 0 ? e : -1
+ case 'switch':
+ case 'synchronized':
+ int sp = s.nextCode(i + w.length())
+ int sc = sp < 0 || s.charAt(sp) != '(' as char ? -1 :
s.matchClose(sp)
+ int sb2 = sc < 0 ? -1 : s.nextCode(sc + 1)
+ if (sb2 < 0 || s.charAt(sb2) != '{' as char) {
+ return -1
+ }
+ int se = s.matchClose(sb2)
+ return se < 0 ? -1 : se + 1
+ default:
+ break
+ }
+ int j = i
+ int n = s.length()
+ while (j < n) {
+ if (!s.isCode(j)) {
+ j++
+ continue
+ }
+ char ch = s.charAt(j)
+ if (ch == '(' as char || ch == '[' as char || ch == '{' as char) {
+ int e = s.matchClose(j)
+ if (e < 0) {
+ return -1
+ }
+ j = e + 1
+ continue
+ }
+ if (ch == ';' as char) {
+ return j + 1
+ }
+ j++
+ }
+ -1
+ }
+
+ // ---- spacing
----------------------------------------------------------------
+
+ static int commentSpace(Src s, int off, String msg, int[] ls) {
+ if (msg.contains('must be followed by a whitespace') && s.has(off,
'//')) {
+ s.insert(off + 2, ' ')
+ return off + 2
+ }
+ NONE
+ }
+
+ // ---- line structure
---------------------------------------------------------
+
+ /** OneStatementPerLine reports the ';' of every statement after the first
on a line. */
+ static int oneStatementPerLine(Src s, int off, String msg, int[] ls) {
+ if (!s.has(off, ';')) {
+ return NONE
+ }
+ int lineStart = s.lineStart(off)
+ int j = off - 1
+ while (j >= lineStart) {
+ if (!s.isCode(j)) {
+ j--
+ continue
+ }
+ char c = s.charAt(j)
+ if (c == ')' as char || c == ']' as char) {
+ int open = s.matchOpen(j)
+ if (open < lineStart) {
+ return NONE
+ }
+ j = open - 1
+ continue
+ }
+ if (c == ';' as char || c == '{' as char || c == '}' as char) {
+ break
+ }
+ j--
+ }
+ if (j < lineStart) {
+ return NONE
+ }
+ int k = j + 1
+ while (k < off && s.isHorizontalWs(k)) {
+ k++
+ }
+ if (k >= off || !s.isCode(k)) {
+ return NONE
+ }
+ s.replace(j + 1, k, nl(s.indentOf(off)))
+ j + 1
+ }
+
+ // ---- wrapping
---------------------------------------------------------------
+
+ /** Operator/'.'/'::' left dangling at a line's end: move it to the start
of the next line. */
+ static int moveToNextLine(Src s, int off, String tok, boolean spaceAfter) {
+ if (!s.has(off, tok)) {
+ return NONE
+ }
+ int lineStart = s.lineStart(off)
+ int le = s.lineEnd(off)
+ String rest = s.slice(off + tok.length(), le).trim()
+ String comment = null
+ if (!rest.isEmpty()) {
+ if (!rest.startsWith('//')) {
+ return NONE
+ }
+ comment = rest
+ }
+ if (le >= s.length() - 1) {
+ return NONE
+ }
+ int nls = le + 1
+ int nfn = s.firstNonWs(nls)
+ if (nfn >= s.lineEnd(nls) || s.isComment(nfn)) {
+ return NONE
+ }
+ int j = off
+ while (j > lineStart && s.isHorizontalWs(j - 1)) {
+ j--
+ }
+ if (j == lineStart) {
+ return NONE
+ }
+ s.insert(nfn, tok + (spaceAfter ? ' ' : ''))
+ s.replace(j, le, comment != null ? ' ' + comment : '')
+ j
+ }
+
+ /** ',' or '...' that starts a line: move it to the end of the previous
line's code. */
+ static int moveToPrevLine(Src s, int off, String tok) {
+ if (!s.has(off, tok)) {
+ return NONE
+ }
+ int lineStart = s.lineStart(off)
+ if (lineStart == 0 || !s.slice(lineStart, off).trim().isEmpty()) {
+ return NONE
+ }
+ int pc = s.prevCode(lineStart - 1)
+ if (pc < 0) {
+ return NONE
+ }
+ int after = off + tok.length()
+ int k = after
+ while (k < s.length() && s.isHorizontalWs(k)) {
+ k++
+ }
+ int le = s.lineEnd(off)
+ if (k >= le) {
+ s.delete(lineStart, Math.min(s.length(), le + 1))
+ } else {
+ s.delete(off, k)
+ }
+ s.insert(pc + 1, tok)
+ pc + 1
+ }
+
+ static int separatorWrap(Src s, int off, String msg, int[] ls) {
+ String tok = tokenOf(msg)
+ if (tok == null) {
+ return NONE
+ }
+ if (msg.contains('should be on a new line')) {
+ String line = s.line(off).trim()
+ if (line.startsWith('import ') || line.startsWith('package ')) {
+ return NONE
+ }
+ return moveToNextLine(s, off, tok, false)
+ }
+ if (msg.contains('should be on the previous line')) {
+ return moveToPrevLine(s, off, tok)
+ }
+ NONE
+ }
+
+ // ---- ordering
---------------------------------------------------------------
+
+ /** Moves the member at off (with its leading comments) to just after the
member at targetLine. */
+ static int moveMemberAfter(Src s, int off, int targetLine, int[]
origLineStarts) {
+ if (targetLine < 1 || targetLine > origLineStarts.length) {
+ return NONE
+ }
+ int cs = leadingBlockStart(s, off)
+ int ce = memberEnd(s, off)
+ int t = s.firstNonWs(origLineStarts[targetLine - 1])
+ int te = t >= cs ? -1 : memberEnd(s, t)
+ if (ce < 0 || te < 0 || te > cs) {
+ return NONE
+ }
+ String member = s.slice(cs, ce)
+ int del = ce
+ if (del < s.length() && s.charAt(del) == '\n' as char) {
+ del++
+ }
+ s.delete(cs, del)
+ if (cs > 0 && s.isBlankLine(s.lineStart(cs - 1)) && (cs >= s.length()
|| s.isBlankLine(cs)
+ || s.line(cs).trim().startsWith('}'))) {
+ int pb = s.lineStart(cs - 1)
+ s.delete(pb, Math.min(s.length(), s.lineEnd(pb) + 1))
+ }
+ s.insert(te, '\n\n' + member)
+ te
+ }
+
+ static int overloadOrder(Src s, int off, String msg, int[] ls) {
+ Matcher m = msg =~ /line '(\d+)'/
+ m.find() ? moveMemberAfter(s, off, m.group(1) as int, ls) : NONE
+ }
+
+ static int constructorGrouping(Src s, int off, String msg, int[] ls) {
+ Matcher m = msg =~ /line '(\d+)'/
+ m.find() ? moveMemberAfter(s, off, m.group(1) as int, ls) : NONE
+ }
+
+
+// ---- literals
---------------------------------------------------------------
+
+private static final Map<Integer, String> ESCAPES = [
+ 8: '\\b', 9: '\\t', 10: '\\n', 12: '\\f', 13: '\\r', 32: ' ', 34:
'\\"', 39: "\\'", 92: '\\\\']
+
+ static int literalEnd(Src s, int off) {
+ int e = off
+ while (e < s.length() && s.isLiteral(e)) {
+ e++
+ }
+ e
+ }
+
+ static int illegalTokenText(Src s, int off, String msg, int[] ls) {
+ if (!s.isLiteral(off)) {
+ return NONE
+ }
+ int start = off
+ while (start > 0 && s.isLiteral(start - 1)) {
+ start--
+ }
+ // text blocks are left alone: there '\u0020' / '\040' keep a trailing
space that a raw space
+ // would lose to the text block's whitespace stripping
+ if (s.has(start, '"""')) {
+ return NONE
+ }
+ int end = literalEnd(s, off)
+ String lit = s.slice(off, end)
+ StringBuilder out = new StringBuilder()
+ int i = 0
+ while (i < lit.length()) {
+ char c = lit.charAt(i)
+ if (c != '\\' as char || i + 1 >= lit.length()) {
+ out.append(c)
+ i++
+ continue
+ }
+ Matcher u = lit.substring(i) =~ /^\\u+([0-9a-fA-F]{4})/
+ Matcher o = lit.substring(i) =~
/^\\(0(?:10|11|12|14|15|40|42|47)|134)(?![0-7])/
+ if (u.find() && ESCAPES.containsKey(Integer.parseInt(u.group(1),
16))) {
+ out.append(ESCAPES[Integer.parseInt(u.group(1), 16)])
+ i += u.end()
+ } else if (o.find()) {
+ out.append(ESCAPES[Integer.parseInt(o.group(1), 8)])
+ i += o.end()
+ } else {
+ out.append(c).append(lit.charAt(i + 1))
+ i += 2
+ }
+ }
+ if (out.toString() == lit) {
+ return NONE
+ }
+ s.replace(off, end, out.toString())
+ off
+ }
+
+ static int avoidEscapedUnicode(Src s, int off, String msg, int[] ls) {
+ int start = off
+ // the reported column may be the string's opening quote or the escape
itself
+ if (!s.isLiteral(off)) {
+ return NONE
+ }
+ while (start > 0 && s.isLiteral(start - 1)) {
+ start--
+ }
+ int end = literalEnd(s, off)
+ String lit = s.slice(start, end)
+ Matcher m = lit =~
/(?<!\\)((?:\\\\)*)\\u+([0-9a-fA-F]{4})(?:\\u+([0-9a-fA-F]{4}))?/
+ StringBuffer out = new StringBuffer()
+ boolean changed = false
+ while (m.find()) {
+ String pair = m.group(3) == null ? '' : m.group(3)
+ String chars = new
String(Character.toChars(Integer.parseInt(m.group(2), 16)))
+ if (pair) {
+ chars += new String(Character.toChars(Integer.parseInt(pair,
16)))
+ if (chars.codePointCount(0, chars.length()) != 1) {
+ chars = new
String(Character.toChars(Integer.parseInt(m.group(2), 16)))
+ m.appendReplacement(out,
Matcher.quoteReplacement(m.group()))
+ continue
+ }
+ }
+ int cp = chars.codePointAt(0)
+ int type = Character.getType(cp)
+ boolean printable = !(Character.isISOControl(cp) ||
Character.isWhitespace(cp) || Character.isSpaceChar(cp)
+ || type == Character.FORMAT || type ==
Character.UNASSIGNED || type == Character.PRIVATE_USE
+ || type == Character.SURROGATE || type ==
Character.LINE_SEPARATOR
+ || type == Character.PARAGRAPH_SEPARATOR || cp == 34 || cp
== 39 || cp == 92)
+ if (!printable || Character.charCount(cp) != chars.length()) {
+ m.appendReplacement(out, Matcher.quoteReplacement(m.group()))
+ continue
+ }
+ m.appendReplacement(out, Matcher.quoteReplacement(m.group(1) +
chars))
+ changed = true
+ }
+ m.appendTail(out)
+ if (!changed) {
+ return NONE
+ }
+ s.replace(start, end, out.toString())
+ start
+ }
+
+ static int todoComment(Src s, int off, String msg, int[] ls) {
+ int i = off
+ while (i < s.length() && s.isHorizontalWs(i)) {
+ i++
+ }
+ if (i + 4 > s.length() || !s.slice(i, i + 4).equalsIgnoreCase('todo')
|| !s.isComment(i)) {
+ return NONE
+ }
+ int e = i + 4
+ if (e < s.length() && s.charAt(e) == ':' as char) {
+ e++
+ }
+ s.replace(i, e, 'TODO:')
+ i
+ }
+
+ // ---- javadoc
----------------------------------------------------------------
+
+ // [start, end) of the javadoc comment enclosing off, or null.
+ static int[] javadocBounds(Src s, int off) {
+ int start = s.lastIndexOf('/**', off)
+ if (start < 0) {
+ return null
+ }
+ int end = s.indexOf('*/', start + 3)
+ if (end < 0 || end + 2 < off) {
+ return null
+ }
+ [start, end + 2] as int[]
+ }
+
+ static int javadocAsteriskAlign(Src s, int off, String msg, int[] ls) {
+ Matcher m = msg =~ /expected is (\d+)/
+ if (!m.find()) {
+ return NONE
+ }
+ int lineStart = s.lineStart(off)
+ int fnw = s.firstNonWs(lineStart)
+ if (fnw >= s.length() || s.charAt(fnw) != '*' as char) {
+ return NONE
+ }
+ s.replace(lineStart, fnw, spaces((m.group(1) as int) - 1))
+ lineStart
+ }
+
+ static int javadocMissingAsterisk(Src s, int off, String msg, int[] ls) {
+ int[] b = javadocBounds(s, off)
+ if (b == null) {
+ return NONE
+ }
+ int col = b[0] - s.lineStart(b[0])
+ int lineStart = s.lineStart(off)
+ String t = s.line(off).trim()
+ String fixed = t.startsWith('*/') ? spaces(col + 1) + t : (t.isEmpty()
? spaces(col + 1) + '*' : spaces(col + 1) + '* ' + t)
+ s.replace(lineStart, s.lineEnd(off), fixed)
+ lineStart
+ }
+
+ static int javadocContentLocation(Src s, int off, String msg, int[] ls) {
+ if (!s.has(off, '/**')) {
+ return NONE
+ }
+ int k = off + 3
+ while (k < s.length() && s.isHorizontalWs(k)) {
+ k++
+ }
+ if (k >= s.length() || s.charAt(k) == '\n' as char || s.has(k, '*/')) {
+ return NONE
+ }
+ int col = off - s.lineStart(off)
+ s.replace(off + 3, k, '\n' + spaces(col + 1) + '* ')
+ off + 3
+ }
+
+ static int requireEmptyLineBeforeTags(Src s, int off, String msg, int[]
ls) {
+ int lineStart = s.lineStart(off)
+ int fnw = s.firstNonWs(lineStart)
+ if (fnw >= s.length() || s.charAt(fnw) != '*' as char || s.has(fnw,
'*/')) {
+ return NONE
+ }
+ s.insert(lineStart, s.slice(lineStart, fnw + 1) + '\n')
+ lineStart
+ }
+
+ static int javadocParagraph(Src s, int off, String msg, int[] ls) {
+ if (msg.contains('should be preceded with an empty line')) {
+ int lineStart = s.lineStart(off)
+ int fnw = s.firstNonWs(lineStart)
+ if (fnw >= s.length() || s.charAt(fnw) != '*' as char ||
!s.slice(fnw + 1, off).trim().isEmpty()) {
+ return NONE
+ }
+ s.insert(lineStart, s.slice(lineStart, fnw + 1) + '\n')
+ return lineStart
+ }
+ if (msg.contains('immediately before the first word')) {
+ if (!s.has(off, '<p>')) {
+ return NONE
+ }
+ int k = off + 3
+ while (k < s.length() && s.isHorizontalWs(k)) {
+ k++
+ }
+ if (k < s.length() && s.charAt(k) == '\n' as char) {
+ int nls = k + 1
+ int nfn = s.firstNonWs(nls)
+ if (nfn >= s.length() || s.charAt(nfn) != '*' as char ||
s.has(nfn, '*/')) {
+ return NONE
+ }
+ int text = nfn + 1
+ while (text < s.length() && s.isHorizontalWs(text)) {
+ text++
+ }
+ if (text >= s.lineEnd(nls)) {
+ return NONE
+ }
+ s.delete(off + 3, text)
+ return off + 3
+ }
+ if (k == off + 3) {
+ return NONE
+ }
+ s.delete(off + 3, k)
+ return off + 3
+ }
+ if (msg.contains('should be followed by <p>')) {
+ int nls = s.lineEnd(off) + 1
+ if (nls >= s.length()) {
+ return NONE
+ }
+ int nfn = s.firstNonWs(nls)
+ if (s.charAt(nfn) != '*' as char || s.has(nfn, '*/')) {
+ return NONE
+ }
+ int text = nfn + 1
+ while (text < s.length() && s.isHorizontalWs(text)) {
+ text++
+ }
+ if (text >= s.lineEnd(nls) || s.charAt(text) == '@' as char ||
s.charAt(text) == '<' as char) {
+ return NONE
+ }
+ s.insert(text, '<p>')
+ return text
+ }
+ NONE
+ }
+
+ static int javadocTagContinuation(Src s, int off, String msg, int[] ls) {
+ int lineStart = s.lineStart(off)
+ Matcher m = s.line(off) =~ /^(\s*\*)\s*(\S.*)$/
+ if (!m.matches()) {
+ return NONE
+ }
+ s.replace(lineStart, s.lineEnd(off), m.group(1) + spaces(basicOffset +
1) + m.group(2))
+ lineStart
+ }
+
+ static int atclauseOrder(Src s, int off, String msg, int[] ls) {
+ int[] b = javadocBounds(s, off)
+ if (b == null) {
+ return NONE
+ }
+ List<String> lines = s.slice(b[0], b[1]).split('\n', -1).toList()
+ if (lines.size() < 3 || lines.last().trim() != '*/') {
+ return NONE
+ }
+ Map<String, Integer> rank = ['@param': 0, '@return': 1, '@throws': 2,
'@deprecated': 3]
+ int first = lines.findIndexOf {
+ it.replaceFirst(/^\s*\*\s?/, '').startsWith('@')
+ }
+ if (first < 0) {
+ return NONE
+ }
+ List<List<String>> blocks = []
+ List<String> tags = []
+ for (int i = first; i < lines.size() - 1; i++) {
+ String content = lines[i].replaceFirst(/^\s*\*\s?/, '')
+ if (content.startsWith('@')) {
+ blocks << []
+ tags << content.split(/[\s{]/)[0]
+ }
+ blocks.last() << lines[i]
+ }
+ List<Integer> slots = (0..<blocks.size()).findAll {
+ rank.containsKey(tags[it])
+ }
+ List<Integer> ordered = slots.sort(false) {
+ rank[tags[it]]
+ }
+ if (slots == ordered) {
+ return NONE
+ }
+ List<List<String>> rebuilt = new ArrayList<>(blocks)
+ for (int k = 0; k < slots.size(); k++) {
+ rebuilt[slots[k]] = blocks[ordered[k]]
+ }
+ List<String> out = lines.subList(0, first) + rebuilt.flatten() +
[lines.last()]
+ s.replace(b[0], b[1], out.join('\n'))
+ b[0]
+ }
+
+ static int summaryJavadoc(Src s, int off, String msg, int[] ls) {
+ // Checkstyle reports these right after the opening slash-star-star:
find the first text char
+ while (off < s.length() && (Character.isWhitespace(s.charAt(off))
+ || (s.charAt(off) == '*' as char && !s.has(off, '*/')))) {
+ off++
+ }
+ if (msg.contains('Forbidden summary fragment')) {
+ Matcher m = s.slice(off, Math.min(s.length(), off + 40)) =~
/^[a-z]+(?=[\s.,])/
+ if (!m.find() || !s.isComment(off)) {
+ return NONE
+ }
+ s.replace(off, off + 1, s.slice(off, off + 1).toUpperCase())
+ return off
+ }
+ if (msg.contains('missing an ending period')) {
+ int lineStart = s.lineStart(off)
+ int lastEnd = -1
+ int cur = lineStart
+ boolean firstLine = true
+ while (cur <= s.length()) {
+ int le = s.lineEnd(cur)
+ int textStart = firstLine ? off : s.firstNonWs(cur)
+ if (!firstLine && textStart < le && s.charAt(textStart) == '*'
as char && !s.has(textStart, '*/')) {
+ textStart++
+ while (textStart < le && s.isHorizontalWs(textStart)) {
+ textStart++
+ }
+ }
+ String content = s.slice(Math.min(textStart, le), le)
+ int close = content.indexOf('*/')
+ if (close >= 0) {
+ content = content.substring(0, close)
+ }
+ String t = content.trim()
+ if (t.isEmpty() || (!firstLine && (t.startsWith('@') ||
t.startsWith('<p>')))) {
+ break
+ }
+ lastEnd = textStart + content.replaceAll(/\s+$/, '').length()
+ if (close >= 0 || le >= s.length()) {
+ break
+ }
+ cur = le + 1
+ firstLine = false
+ }
+ if (lastEnd < 0) {
+ return NONE
+ }
+ char last = s.charAt(lastEnd - 1)
+ if (last == ':' as char || last == '>' as char || last == '.' as
char) {
+ return NONE
+ }
+ s.insert(lastEnd, '.')
+ return lastEnd
+ }
+ NONE
+ }
+
+ static int invalidJavadocPosition(Src s, int off, String msg, int[] ls) {
+ if (s.has(off, '/**') && !s.has(off, '/**/')) {
+ s.replace(off, off + 3, '/*')
+ return off
+ }
+ NONE
+ }
+
+ // ---- indentation
------------------------------------------------------------
+
+ // ---- line length
------------------------------------------------------------
+
+ static int lineLength(Src s, int off, String msg, int[] ls) {
+ int lineStart = s.lineStart(off)
+ int le = s.lineEnd(off)
+ String line = s.slice(lineStart, le)
+ int max = maxLineLength
+ if (line.length() <= max) {
+ return NONE
+ }
+ int fnw = s.firstNonWs(lineStart)
+ if (fnw >= le) {
+ return NONE
+ }
+ if (s.isComment(fnw)) {
+ return wrapComment(s, lineStart, fnw, le, max)
+ }
+ wrapCode(s, lineStart, fnw, le, max)
+ }
+
+ private static int wrapComment(Src s, int lineStart, int fnw, int le, int
max) {
+ String line = s.slice(lineStart, le)
+ if (line =~ /(https?|ftp):\/\/|href\s*=/) {
+ return NONE
+ }
+ String prefix
+ String content
+ if (s.has(fnw, '//')) {
+ int t = fnw + 2
+ while (t < le && s.isHorizontalWs(t)) {
+ t++
+ }
+ prefix = s.slice(lineStart, fnw) + '// '
+ content = s.slice(t, le).trim()
+ } else if (s.charAt(fnw) == '*' as char && !s.has(fnw, '*/')) {
+ int t = fnw + 1
+ while (t < le && s.isHorizontalWs(t)) {
+ t++
+ }
+ content = s.slice(t, le).trim()
+ if (content.contains('*/') || content.startsWith('<pre') ||
content.contains('{@code')) {
+ return NONE
+ }
+ String extra = ''
+ // continuation of a block tag is indented one extra level; skip
preformatted text
+ int cur = lineStart
+ while (cur > 0) {
+ int pls = s.lineStart(cur - 1)
+ String pl = s.line(pls).trim().replaceFirst(/^\/?\*+\s?/, '')
+ if (pl.contains('<pre>') && !pl.contains('</pre>')) {
+ return NONE
+ }
+ if (pl.startsWith('@')) {
+ extra = spaces(basicOffset)
+ break
+ }
+ if (pl.isEmpty() || s.line(pls).trim().startsWith('/**') ||
s.line(pls).trim().startsWith('/*')) {
+ break
+ }
+ cur = pls
+ }
+ if (content.startsWith('@') || !extra.isEmpty()) {
+ extra = spaces(basicOffset)
+ }
+ prefix = s.slice(lineStart, fnw + 1) + ' ' +
(content.startsWith('@') ? '' : extra)
+ if (content.startsWith('@')) {
+ // wrapped tag lines continue indented
+ prefix = s.slice(lineStart, fnw + 1) + ' ' +
spaces(basicOffset)
+ return wrapWords(s, lineStart, le, s.slice(lineStart, fnw + 1)
+ ' ', prefix, content, max)
+ }
+ return wrapWords(s, lineStart, le, s.slice(lineStart, fnw + 1) + '
' + extra, prefix, content, max)
+ } else {
+ return NONE
+ }
+ wrapWords(s, lineStart, le, prefix, prefix, content, max)
+ }
+
+ private static int wrapWords(Src s, int lineStart, int le, String
firstPrefix, String nextPrefix, String content, int max) {
+ List<String> words = content.split(/\s+/).toList()
+ List<String> out = []
+ StringBuilder cur = new StringBuilder(firstPrefix)
+ boolean empty = true
+ for (String w : words) {
+ int add = (empty ? 0 : 1) + w.length()
+ if (!empty && cur.length() + add > max) {
+ out << cur.toString()
+ cur = new StringBuilder(nextPrefix)
+ empty = true
+ add = w.length()
+ }
+ if (!empty) {
+ cur.append(' ')
+ }
+ cur.append(w)
+ empty = false
+ }
+ out << cur.toString()
+ if (out.size() < 2 || out.any {
+ it.length() > max && it.trim().split(/\s+/).size() > 2
+ }) {
+ if (out.size() < 2) {
+ return NONE
+ }
+ }
+ s.replace(lineStart, le, out.join('\n'))
+ lineStart
+ }
+
+ private static int wrapCode(Src s, int lineStart, int fnw, int le, int
max) {
+ String head = s.slice(fnw, Math.min(le, fnw + 8))
+ if (head.startsWith('import ') || head.startsWith('package ')) {
+ return NONE
+ }
+ // candidate break offsets, all inside code
+ int best = -1
+ int depth = 0
+ for (int i = fnw; i < le; i++) {
+ if (!s.isCode(i)) {
+ continue
+ }
+ char c = s.charAt(i)
+ if (c == '(' as char || c == '[' as char) {
+ depth++
+ } else if (c == ')' as char || c == ']' as char) {
+ depth--
+ }
+ int pos = -1
+ if (c == ',' as char && depth > 0) {
+ pos = i + 1
+ } else if ((s.has(i, '&&') || s.has(i, '||')) && i > fnw &&
s.isHorizontalWs(i - 1)) {
+ pos = i
+ } else if (c == '+' as char && i > fnw && s.isHorizontalWs(i - 1)
&& i + 1 < le && s.isHorizontalWs(i + 1)) {
+ pos = i
+ } else if (c == '?' as char && i > fnw && s.isHorizontalWs(i - 1)
&& i + 1 < le && s.isHorizontalWs(i + 1)) {
+ pos = i
+ } else if (c == '.' as char && i > fnw && i + 1 < le &&
Character.isJavaIdentifierStart(s.charAt(i + 1))
+ && (s.charAt(i - 1) == ')' as char ||
Character.isJavaIdentifierPart(s.charAt(i - 1)))) {
+ String after = s.slice(i + 1, le)
+ if (after =~ /^\w+\s*\(/) {
+ pos = i
+ }
+ }
+ if (pos > fnw && pos - lineStart <= max && (pos - lineStart) >
(fnw - lineStart) + 8) {
+ best = pos
+ }
+ }
+ if (best < 0) {
+ for (int i = fnw; i < le; i++) {
+ if (s.isCode(i) && s.charAt(i) == '=' as char &&
s.isHorizontalWs(i - 1) && i + 1 < le && s.isHorizontalWs(i + 1)
+ && i + 1 - lineStart <= max && i + 1 > fnw + 8) {
+ best = i + 1
+ }
+ }
+ }
+ if (best < 0) {
+ return splitString(s, lineStart, fnw, le, max)
+ }
+ int trimEnd = best
+ while (trimEnd > lineStart && s.isHorizontalWs(trimEnd - 1)) {
+ trimEnd--
+ }
+ int restStart = best
+ while (restStart < le && s.isHorizontalWs(restStart)) {
+ restStart++
+ }
+ if (restStart >= le) {
+ return NONE
+ }
+ s.replace(trimEnd, restStart, nl(continuationIndent(s, lineStart,
fnw)))
+ trimEnd
+ }
+
+ /** Indent for a wrapped continuation: keep an existing continuation's
indent, else statement indent + 2 levels. */
+ static String continuationIndent(Src s, int lineStart, int fnw) {
+ String indent = s.slice(lineStart, fnw)
+ int p = lineStart > 0 ? s.prevCode(lineStart - 1) : -1
+ boolean statementStart = p < 0 || s.charAt(p) == ';' as char ||
s.charAt(p) == '{' as char
+ || s.charAt(p) == '}' as char
+ statementStart ? indent + spaces(2 * basicOffset) : indent
+ }
+
+ private static int splitString(Src s, int lineStart, int fnw, int le, int
max) {
+ for (int i = fnw; i < le; i++) {
+ if (!s.isLiteral(i) || s.charAt(i) != '"' as char || (i > 0 &&
s.isLiteral(i - 1))) {
+ continue
+ }
+ int end = literalEnd(s, i)
+ if (end - i < 6 || s.has(i, '"""') || end <= lineStart + max - 1
|| end > le) {
+ continue
+ }
+ int prev = s.prevCode(i - 1)
+ int next = s.nextCode(end)
+ if (prev < 0 || next < 0) {
+ return NONE
+ }
+ char pc = s.charAt(prev)
+ String pw = Character.isJavaIdentifierPart(pc) ?
s.slice(Math.max(0, prev - 5), prev + 1) : ''
+ char nc = s.charAt(next)
+ boolean okPrev = pc in ['(', ',', '=', '+', '?', ':', '{'] as
char[] || pw.endsWith('return') || pw.endsWith('case')
+ boolean okNext = nc in [',', ')', ';', '+', '?', ':', '}'] as
char[]
+ if (!okPrev || !okNext) {
+ return NONE
+ }
+ // safe split points: boundaries that don't cut an escape sequence
+ List<Integer> safe = []
+ int k = i + 1
+ while (k < end - 1) {
+ if (s.charAt(k) == '\\' as char) {
+ // a whole escape: unicode, octal (up to three digits,
\0-\377) or single character
+ Matcher e = s.slice(k, end) =~
/^\\(u+[0-9a-fA-F]{4}|[0-3][0-7]{0,2}|[4-7][0-7]?)/
+ k += e.find() ? e.end() : 2
+ } else {
+ k++
+ }
+ safe << k
+ }
+ int limit = lineStart + max - 2
+ int cut = -1
+ for (int cand : safe) {
+ if (cand <= limit && cand > i + 8 && s.charAt(cand - 1) == ' '
as char) {
+ cut = cand
+ }
+ }
+ if (cut < 0) {
+ for (int cand : safe) {
+ if (cand <= limit && cand > i + 8) {
+ cut = cand
+ }
+ }
+ }
+ if (cut < 0 || cut >= end - 1) {
+ return NONE
+ }
+ s.replace(cut, cut, '"' + nl(continuationIndent(s, lineStart,
fnw)) + '+ "')
+ return cut
+ }
+ NONE
+ }
+}
+
+//
=================================================================================
+// Script body
+//
=================================================================================
+
+//
---------------------------------------------------------------------------------
+// Load the ruleset directly - the same file maven-checkstyle-plugin's
configLocation uses (root pom).
+//
---------------------------------------------------------------------------------
+
+String multiModuleDir = System.getProperty('maven.multiModuleProjectDirectory')
+File rulesetFile = new
File("${multiModuleDir}/storm-checkstyle/src/main/resources/storm/storm_checkstyle.xml")
+if (!rulesetFile.exists()) {
+ println "[checkstyle-autofix] ${project.artifactId}: ruleset
${rulesetFile} not found, skipping."
+ return
+}
+
+// Checkstyle rulesets always carry a <!DOCTYPE module PUBLIC ...> pointing at
the
+// public checkstyle DTD. XmlSlurper's default SAXParser rejects any DOCTYPE
outright
+// (disallow-doctype-decl=true, a blanket XXE hardening default), so it must
be turned
+// off here - but external entity/DTD fetching stays disabled so this doesn't
reopen
+// an XXE hole; the doctype is parsed, its external subset never is.
+def xmlSlurper = new XmlSlurper()
+xmlSlurper.setFeature('http://apache.org/xml/features/disallow-doctype-decl',
false)
+xmlSlurper.setFeature('http://xml.org/sax/features/external-general-entities',
false)
+xmlSlurper.setFeature('http://xml.org/sax/features/external-parameter-entities',
false)
+xmlSlurper.setFeature('http://apache.org/xml/features/nonvalidating/load-external-dtd',
false)
+def checker = xmlSlurper.parse(rulesetFile)
+def treeWalker = checker.module.find {
+ it.@name == 'TreeWalker'
+}
+
+def findModule = { String name ->
+ treeWalker.module.find {
+ it.@name == name
+ }
+}
+def moduleProperty = { module, String propName, String defaultValue = null ->
+ def prop = module?.property?.find {
+ it.@name == propName
+ }
+ prop ? [email protected]() : defaultValue
+}
+
+Fixers.basicOffset = moduleProperty(findModule('Indentation'), 'basicOffset',
'4').toInteger()
+Fixers.maxLineLength = moduleProperty(checker.module.find {
+ it.@name == 'LineLength'
+}, 'max', '100').toInteger()
+
+
+def sourceRoots = ((project.compileSourceRoots ?: []) +
(project.testCompileSourceRoots ?: [])).unique()
+// Honour the same <excludes> the module gives maven-checkstyle-plugin (e.g.
thrift-generated
+// code): those files aren't checked, so they must not be rewritten - or even
audited, which
+// for the huge generated classes is very slow.
+def checkstylePlugin = project.build?.plugins?.find {
+ it.artifactId == 'maven-checkstyle-plugin'
+}
+List<java.nio.file.PathMatcher> excludeMatchers =
(checkstylePlugin?.configuration?.getChild('excludes')?.value ?: '')
+ .split(',').collect {
+ it.trim()
+}.findAll {
+ it
+}
+ .collect {
+
java.nio.file.FileSystems.default.getPathMatcher("glob:/${it}".toString())
+ }
+List<File> javaFiles = []
+sourceRoots.each { rootPath ->
+ File rootDir = new File(rootPath)
+ if (rootDir.exists()) {
+ rootDir.eachFileRecurse(FileType.FILES) { file ->
+ String relative = '/' +
rootDir.toPath().relativize(file.toPath()).toString().replace(File.separator,
'/')
+ if (file.name.endsWith('.java') && !excludeMatchers.any {
+ it.matches(java.nio.file.Paths.get(relative))
+ }) {
+ javaFiles << file
+ }
+ }
+ }
+}
+
+// Stage 2: audit with Checkstyle, fix what each reported violation points at,
repeat.
+Map<String, Closure> fixers = [
+ OneStatementPerLine : Fixers.&oneStatementPerLine,
+ LineLength : Fixers.&lineLength,
+ MatchXpath : Fixers.&commentSpace,
+ SeparatorWrap : Fixers.&separatorWrap,
+ OverloadMethodsDeclarationOrder : Fixers.&overloadOrder,
+ ConstructorsDeclarationGrouping : Fixers.&constructorGrouping,
+ IllegalTokenText : Fixers.&illegalTokenText,
+ AvoidEscapedUnicodeCharacters : Fixers.&avoidEscapedUnicode,
+ TodoComment : Fixers.&todoComment,
+ JavadocLeadingAsteriskAlign : Fixers.&javadocAsteriskAlign,
+ JavadocMissingLeadingAsterisk : Fixers.&javadocMissingAsterisk,
+ JavadocContentLocation : Fixers.&javadocContentLocation,
+ JavadocParagraph : Fixers.&javadocParagraph,
+ JavadocTagContinuationIndentation : Fixers.&javadocTagContinuation,
+ RequireEmptyLineBeforeBlockTagGroup:
Fixers.&requireEmptyLineBeforeTags,
+ AtclauseOrder : Fixers.&atclauseOrder,
+ SummaryJavadoc : Fixers.&summaryJavadoc,
+ InvalidJavadocPosition : Fixers.&invalidJavadocPosition,
+]
+
+final int MAX_ROUNDS = 25
+
+Map<String, List<Map>> violations = [:]
+Set<String> unparsable = [] as Set
+
+Properties checkstyleProps = new Properties()
+checkstyleProps.setProperty('org.checkstyle.google.severity', 'error')
+def config = ConfigurationLoader.loadConfiguration(rulesetFile.absolutePath,
new PropertiesExpander(checkstyleProps))
+def auditor = new Checker()
+auditor.setModuleClassLoader(Checker.classLoader)
+// columns then equal char offsets + 1 even if a tab slips through
+auditor.setTabWidth(1)
+auditor.configure(config)
+auditor.addListener([
+ auditStarted : { AuditEvent e ->
+ },
+ auditFinished: { AuditEvent e ->
+ },
+ fileStarted : { AuditEvent e ->
+ },
+ fileFinished : { AuditEvent e ->
+ },
+ addError : { AuditEvent e ->
+ String key = e.sourceName.substring(e.sourceName.lastIndexOf('.')
+ 1).replaceFirst(/Check$/, '')
+ violations.get(e.fileName, []) << [line: e.line, col: e.column,
msg: e.message, check: key]
+ },
+ addException : { AuditEvent e, Throwable t ->
+ unparsable << e.fileName
+ },
+] as AuditListener)
+
+// One file per call: Checkstyle throws out of process() on a file that
doesn't parse.
+def audit = { List<File> files ->
+ violations.clear()
+ unparsable.clear()
+ files.each { File f ->
+ try {
+ auditor.process([f])
+ } catch (Exception ex) {
+ unparsable << f.absolutePath
+ }
+ }
+}
+
+Map<File, String> originalText = [:]
+Map<File, String> beforeRound = [:]
+Map<File, Integer> initialCount = [:]
+Map<File, Integer> latestCount = [:]
+Map<String, Integer> appliedByCheck = [:].withDefault {
+ 0
+}
+Set<File> frozen = [] as Set
+Map<String, String> fixerErrors = [:]
+
+// skip CRLF files: the fixers write '\n'
+List<File> pending = javaFiles.findAll { File f ->
+ !f.getText('UTF-8').contains('\r')
+}
+int round = 0
+while (!pending.isEmpty() && round < MAX_ROUNDS) {
+ round++
+ pending.each { File f ->
+ originalText.putIfAbsent(f, f.getText('UTF-8'))
+ }
+ audit(pending)
+ List<File> next = []
+ pending.each { File f ->
+ List<Map> vs = violations[f.absolutePath] ?: []
+ latestCount[f] = vs.size()
+ if (!initialCount.containsKey(f)) {
+ initialCount[f] = vs.size()
+ }
+ if (unparsable.contains(f.absolutePath)) {
+ if (beforeRound.containsKey(f)) {
+ f.setText(beforeRound[f], 'UTF-8')
+ frozen << f
+ println "[checkstyle-autofix] ${f}: a fix left the file
unparsable; reverted the last round."
+ }
+ return
+ }
+ String text = f.getText('UTF-8')
+ List<Map> fixable = vs.findAll {
+ fixers.containsKey(it.check)
+ }
+ if (fixable.isEmpty()) {
+ return
+ }
+ int[] lineStarts = lineStartsOf(text)
+ Src src = new Src(text)
+ int headerEnd = licenseHeaderEnd(text)
+ List<Map> work = fixable.collect { Map v ->
+ v + [off: v.line >= 1 && v.line <= lineStarts.length ?
lineStarts[v.line - 1] + Math.max(0, v.col - 1) : -1]
+ }.findAll {
+ // nothing reported inside the license header is fixed
+ it.off >= headerEnd
+ }
+ // last-to-first; on ties handle the token's trailing side first
+ work.sort { a, b ->
+ b.off <=> a.off
+ }
+ int lowest = Integer.MAX_VALUE
+ work.each { Map v ->
+ if (v.off >= lowest) {
+ return
+ }
+ int touched
+ try {
+ touched = (fixers[v.check].call(src, v.off, v.msg, lineStarts)
as int)
+ } catch (Exception ex) {
+ fixerErrors[v.check] = "${ex.class.simpleName}:
${ex.message}".toString()
+ touched = Fixers.NONE
+ }
+ if (touched >= 0) {
+ lowest = Math.min(lowest, touched)
+ appliedByCheck[v.check]++
+ }
+ }
+ String updated = src.toString()
+ if (updated != text) {
+ beforeRound[f] = text
+ f.setText(updated, 'UTF-8')
+ next << f
+ }
+ }
+ pending = next.findAll {
+ !frozen.contains(it)
+ }
+}
+if (!pending.isEmpty()) {
+ audit(pending)
+ pending.each { File f ->
+ latestCount[f] = (violations[f.absolutePath] ?: []).size()
+ }
+}
+
+// A file that ended with more violations than it started with is put back as
it was.
+int filesChanged = 0
+originalText.each { File f, String original ->
+ if (f.getText('UTF-8') == original) {
+ return
+ }
+ if (latestCount[f] != null && initialCount[f] != null && latestCount[f] >
initialCount[f]) {
+ f.setText(original, 'UTF-8')
+ println "[checkstyle-autofix] ${f}: fixing made it worse
(${initialCount[f]} -> ${latestCount[f]} violations); restored."
+ } else {
+ filesChanged++
+ }
+}
+auditor.destroy()
+
+println "[checkstyle-autofix] ${project.artifactId}: " +
+ "fixes applied ${appliedByCheck.sort()} over ${round} round(s),
${filesChanged} file(s) changed, " +
+ "ruleset=storm_checkstyle.xml." + (fixerErrors ? " Fixer errors (bug
in the fixer, violation left as is): ${fixerErrors}" : '')
+
+/** End of the license comment at the top of the file, 0 if the file doesn't
start with one. */
+static int licenseHeaderEnd(String text) {
+ Matcher m = text =~ /(?s)\A\s*\/\*.*?\*\//
+ m.find() && m.group().contains('License') ? m.end() : 0
+}
+
+static int[] lineStartsOf(String text) {
+ List<Integer> starts = [0]
+ int i = text.indexOf('\n')
+ while (i >= 0) {
+ starts << i + 1
+ i = text.indexOf('\n', i + 1)
+ }
+ starts as int[]
+}
diff --git
a/storm-checkstyle/src/main/resources/storm/normalize-license-headers.groovy
b/storm-checkstyle/src/main/resources/storm/normalize-license-headers.groovy
new file mode 100644
index 000000000..10f4a1c51
--- /dev/null
+++ b/storm-checkstyle/src/main/resources/storm/normalize-license-headers.groovy
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Rewrites the leading license comment of the files of a module (Java,
Groovy, XML, Markdown,
+ * YAML, properties, Python and shell) to the exact template of
+ * https://www.apache.org/legal/src-headers.html#headers.
+ *
+ * A file is left alone when its leading comment is not an ASF license header,
when it carries
+ * a copyright notice of anybody but the ASF (third-party works keep their own
notice), or when its
+ * header already has the words of the template, whatever the line wrapping,
the case (the older
+ * 'to You' form) or the Javadoc '<p>' an IDE formatter adds: rewrapping such
a header would touch
+ * most files of the code base and pollute git blame for no legal gain. So
only headers whose
+ * wording is wrong (typos, missing words) are rewritten.
+ *
+ * Only the license lines are replaced: a block or XML comment with anything
besides the license is
+ * left alone, and the '#' lines around the license (e.g. a '-*- coding -*-'
line) are kept.
+ *
+ * Run on demand via gmavenplus-plugin's `execute` goal (execution id
`normalize-license-headers`,
+ * bound to no phase); see storm-checkstyle/README.md. With
-Dlicense.check=true nothing is
+ * written and the build fails if a file would change.
+ */
+
+final List<String> TEXT = '''\
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.'''.readLines()
+
+boolean checkOnly = Boolean.getBoolean('license.check')
+
+// the words of a comment, without comment markers and line wrapping
+String words(String comment) {
+ return comment.readLines()
+ .collect { it.replaceAll(/\*\/|-->|<p\s*\/?>/,
'').replaceFirst(/^\s*(\/\*+|\*|#|<!--|~)?/, '') }
+ .join(' ').split(/\s+/).findAll().join(' ')
+}
+
+templateWords = words(TEXT.join('\n')).toLowerCase()
+
+blockHeader = '/*\n' + TEXT.collect { it.isEmpty() ? ' *' : ' * ' + it
}.join('\n') + '\n */\n'
+hashHeader = TEXT.collect { it.isEmpty() ? '#' : '# ' + it }.join('\n') + '\n'
+xmlHeader = '<!--\n' + TEXT.collect { it.isEmpty() ? '' : ' ' + it
}.join('\n') + '\n-->\n'
+
+boolean isAsfLicense(String comment) {
+ if (!comment.contains('Apache Software Foundation') ||
!(comment.contains('Licensed to') || comment.contains('Licensed under'))) {
+ return false
+ }
+ // a copyright line of somebody else: a third-party work
+ return !comment.readLines().any { it.contains('Copyright') &&
!it.contains('Apache Software Foundation') }
+}
+
+// a comment holding the license and nothing else, and not already worded as
the template
+boolean needsRewrite(String comment) {
+ String w = words(comment)
+ return isAsfLicense(comment) && w.startsWith('Licensed ') &&
w.endsWith('under the License.') && w.toLowerCase() != templateWords
+}
+
+// '/* */' comment at the top of the file
+String fixBlock(String text, String header) {
+ def m = (text =~ /(?s)\A\s*\/\*.*?\*\/[ \t]*\n?/)
+ if (!m.find() || !needsRewrite(m.group())) {
+ return text
+ }
+ return header + '\n' + text.substring(m.end()).replaceFirst(/^\n+/, '')
+}
+
+// '#' comment lines at the top, after an optional shebang: only the license
lines are replaced
+String fixHash(String text, String header) {
+ String prefix = ''
+ String rest = text
+ if (rest.startsWith('#!')) {
+ int nl = rest.indexOf('\n')
+ prefix = rest.substring(0, nl + 1)
+ rest = rest.substring(nl + 1)
+ }
+ def m = (rest =~ /\A(\s*)((?:[ \t]*#[^\n]*(?:\n|\z))+)/)
+ if (!m.find()) {
+ return text
+ }
+ // the license runs from the 'Licensed ...' line to the '... under the
License.' line
+ List<String> lines = m.group(2).readLines()
+ int start = lines.findIndexOf { it =~ /Licensed (to|under)/ }
+ int end = start < 0 ? -1 : lines.findIndexOf(start) { it.contains('under
the License.') }
+ if (end < 0 || !needsRewrite(lines[start..end].join('\n'))) {
+ return text
+ }
+ List<String> run = lines.subList(0, start) + header.readLines() +
lines.subList(end + 1, lines.size())
+ return prefix + m.group(1) + run.join('\n') + (m.group(2).endsWith('\n') ?
'\n' : '') + rest.substring(m.end())
+}
+
+// '<!-- -->' comment at the top, after an optional '<?xml ...?>' declaration
+String fixXml(String text, String header) {
+ String prefix = ''
+ String rest = text
+ def decl = (rest =~ /\A<\?xml[^>]*\?>[ \t]*\n/)
+ if (decl.find()) {
+ prefix = decl.group()
+ rest = rest.substring(decl.end()).replaceFirst(/^\n+/, '')
+ }
+ def m = (rest =~ /(?s)\A\s*<!--.*?-->[ \t]*\n?/)
+ if (!m.find() || !needsRewrite(m.group())) {
+ return text
+ }
+ String after = rest.substring(m.end()).replaceFirst(/^\n+/, '')
+ return prefix + header + (after ? '\n' + after : '')
+}
+
+Closure fixerFor(String name) {
+ if (name.endsWith('.java') || name.endsWith('.groovy')) {
+ return { String t -> fixBlock(t, blockHeader) }
+ }
+ if (name.endsWith('.xml') || name.endsWith('.md')) {
+ return { String t -> fixXml(t, xmlHeader) }
+ }
+ if (['.yaml', '.yml', '.properties', '.py', '.sh'].any { name.endsWith(it)
}) {
+ return { String t -> fixHash(t, hashHeader) }
+ }
+ return null
+}
+
+Set<String> skippedDirs = ['target', 'node_modules', 'generated', '.git'] as
Set
+List<String> changed = []
+File base = project.basedir
+base.eachFileRecurse(groovy.io.FileType.FILES) { File f ->
+ String rel = base.toPath().relativize(f.toPath()).toString()
+ if (rel.split('/').any { skippedDirs.contains(it) }) {
+ return
+ }
+ Closure fix = fixerFor(f.name)
+ if (fix == null) {
+ return
+ }
+ String text = f.getText('UTF-8')
+ String fixed = fix(text)
+ if (fixed != text) {
+ changed << rel
+ if (!checkOnly) {
+ f.setText(fixed, 'UTF-8')
+ }
+ }
+}
+
+println "[normalize-license-headers] ${project.artifactId}: ${changed.size()}
file(s) ${checkOnly ? 'need a header fix' : 'rewritten'}"
+if (checkOnly && !changed.isEmpty()) {
+ changed.each { println " ${it}" }
+ throw new IllegalStateException('License headers differ from the ASF
template')
+}
diff --git a/storm-checkstyle/src/main/resources/storm/storm_checkstyle.xml
b/storm-checkstyle/src/main/resources/storm/storm_checkstyle.xml
index ab9265d1e..006741a7b 100644
--- a/storm-checkstyle/src/main/resources/storm/storm_checkstyle.xml
+++ b/storm-checkstyle/src/main/resources/storm/storm_checkstyle.xml
@@ -64,6 +64,15 @@
<property name="lineRange" value="$2"/>
</module>
+ <!-- Both wrappings of the ASF license header are accepted; the Javadoc
one ('/**') trips
+ JavadocParagraph on its blank lines, so the check is muted on the
header only: from the
+ header's last text line (ending in 'under the License.') back to the
top of the file. -->
+ <module name="SuppressWithNearbyTextFilter">
+ <property name="nearbyTextPattern" value="^ \* (.* )?under the
License\.$"/>
+ <property name="checkPattern" value="JavadocParagraph"/>
+ <property name="lineRange" value="-20"/>
+ </module>
+
<!-- Checks for whitespace -->
<!-- See http://checkstyle.org/checks/whitespace/index.html -->
<module name="FileTabCharacter">
diff --git a/storm-client/pom.xml b/storm-client/pom.xml
index cfa566830..a97f712da 100644
--- a/storm-client/pom.xml
+++ b/storm-client/pom.xml
@@ -199,6 +199,16 @@
<reportsDirectory>${project.build.directory}/test-reports</reportsDirectory>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/storm-core/pom.xml b/storm-core/pom.xml
index 1f0ef1d74..3840e05d5 100644
--- a/storm-core/pom.xml
+++ b/storm-core/pom.xml
@@ -233,6 +233,16 @@
</testResource>
</testResources>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/storm-multilang/javascript/pom.xml
b/storm-multilang/javascript/pom.xml
index edff4460c..c063db86a 100644
--- a/storm-multilang/javascript/pom.xml
+++ b/storm-multilang/javascript/pom.xml
@@ -33,6 +33,16 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/storm-multilang/ruby/pom.xml b/storm-multilang/ruby/pom.xml
index 291a3cdcd..ae84aee31 100644
--- a/storm-multilang/ruby/pom.xml
+++ b/storm-multilang/ruby/pom.xml
@@ -33,6 +33,16 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/storm-server/pom.xml b/storm-server/pom.xml
index ae859c3f5..b1ec5c8f8 100644
--- a/storm-server/pom.xml
+++ b/storm-server/pom.xml
@@ -198,6 +198,16 @@
<reportsDirectory>${project.build.directory}/test-reports</reportsDirectory>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/storm-submit-tools/pom.xml b/storm-submit-tools/pom.xml
index c3e2a7737..ea65fc86a 100644
--- a/storm-submit-tools/pom.xml
+++ b/storm-submit-tools/pom.xml
@@ -112,6 +112,16 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
diff --git a/storm-webapp/pom.xml b/storm-webapp/pom.xml
index 20a0c48aa..4f6e5d0ac 100644
--- a/storm-webapp/pom.xml
+++ b/storm-webapp/pom.xml
@@ -249,6 +249,16 @@
<reportsDirectory>${project.build.directory}/test-reports</reportsDirectory>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.openrewrite.maven</groupId>
+ <artifactId>rewrite-maven-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <!--Note - the version/config would be inherited-->
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>