Add simple calculator project #7
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: java-linter | |
| on: | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - '**.java' | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| checks: write | |
| jobs: | |
| java-lint-and-review: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Fetch PR base branch | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| git fetch origin ${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }} | |
| - name: Detect build tool | |
| id: detect-build | |
| run: | | |
| if [ -f "pom.xml" ]; then | |
| echo "build_tool=maven" >> $GITHUB_OUTPUT | |
| echo "Build tool: Maven" | |
| elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then | |
| echo "build_tool=gradle" >> $GITHUB_OUTPUT | |
| echo "Build tool: Gradle" | |
| else | |
| echo "build_tool=none" >> $GITHUB_OUTPUT | |
| echo "No build tool detected" | |
| fi | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| cache: ${{ steps.detect-build.outputs.build_tool != 'none' && steps.detect-build.outputs.build_tool || '' }} | |
| - name: Cache dependencies | |
| uses: actions/cache@v3 | |
| if: steps.detect-build.outputs.build_tool != 'none' | |
| with: | |
| path: | | |
| ~/.m2/repository | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-${{ steps.detect-build.outputs.build_tool }}-${{ hashFiles('**/pom.xml', '**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ steps.detect-build.outputs.build_tool }}- | |
| - name: Install Checkstyle | |
| run: | | |
| mkdir -p $HOME/tools | |
| cd $HOME/tools | |
| wget https://github.com/checkstyle/checkstyle/releases/download/checkstyle-10.12.5/checkstyle-10.12.5-all.jar | |
| echo "CHECKSTYLE_JAR=$HOME/tools/checkstyle-10.12.5-all.jar" >> $GITHUB_ENV | |
| - name: Download Google Java Format | |
| run: | | |
| cd $HOME/tools | |
| wget https://github.com/google/google-java-format/releases/download/v1.19.1/google-java-format-1.19.1-all-deps.jar | |
| echo "FORMATTER_JAR=$HOME/tools/google-java-format-1.19.1-all-deps.jar" >> $GITHUB_ENV | |
| - name: Create Checkstyle Configuration | |
| run: | | |
| cat > checkstyle.xml << 'EOF' | |
| <?xml version="1.0"?> | |
| <!DOCTYPE module PUBLIC | |
| "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" | |
| "https://checkstyle.org/dtds/configuration_1_3.dtd"> | |
| <module name="Checker"> | |
| <property name="charset" value="UTF-8"/> | |
| <property name="severity" value="warning"/> | |
| <property name="fileExtensions" value="java"/> | |
| <!-- Line length check - must be under Checker --> | |
| <module name="LineLength"> | |
| <property name="max" value="120"/> | |
| <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/> | |
| </module> | |
| <module name="TreeWalker"> | |
| <module name="OuterTypeFilename"/> | |
| <module name="IllegalTokenText"> | |
| <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/> | |
| <property name="format" value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/> | |
| </module> | |
| <module name="AvoidEscapedUnicodeCharacters"> | |
| <property name="allowEscapesForControlCharacters" value="true"/> | |
| <property name="allowByTailComment" value="true"/> | |
| <property name="allowNonPrintableEscapes" value="true"/> | |
| </module> | |
| <module name="AvoidStarImport"/> | |
| <module name="OneTopLevelClass"/> | |
| <module name="NoLineWrap"/> | |
| <module name="EmptyBlock"> | |
| <property name="option" value="TEXT"/> | |
| <property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> | |
| </module> | |
| <module name="NeedBraces"/> | |
| <module name="LeftCurly"/> | |
| <module name="RightCurly"> | |
| <property name="option" value="alone"/> | |
| <property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, INSTANCE_INIT"/> | |
| </module> | |
| <module name="WhitespaceAround"> | |
| <property name="allowEmptyConstructors" value="true"/> | |
| <property name="allowEmptyMethods" value="true"/> | |
| <property name="allowEmptyTypes" value="true"/> | |
| <property name="allowEmptyLoops" value="true"/> | |
| </module> | |
| <module name="OneStatementPerLine"/> | |
| <module name="MultipleVariableDeclarations"/> | |
| <module name="ArrayTypeStyle"/> | |
| <module name="MissingSwitchDefault"/> | |
| <module name="FallThrough"/> | |
| <module name="UpperEll"/> | |
| <module name="ModifierOrder"/> | |
| <module name="EmptyLineSeparator"> | |
| <property name="allowNoEmptyLineBetweenFields" value="true"/> | |
| </module> | |
| <module name="SeparatorWrap"> | |
| <property name="tokens" value="DOT"/> | |
| <property name="option" value="nl"/> | |
| </module> | |
| <module name="SeparatorWrap"> | |
| <property name="tokens" value="COMMA"/> | |
| <property name="option" value="EOL"/> | |
| </module> | |
| <module name="PackageName"> | |
| <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/> | |
| </module> | |
| <module name="TypeName"/> | |
| <module name="MemberName"> | |
| <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/> | |
| </module> | |
| <module name="ParameterName"> | |
| <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> | |
| </module> | |
| <module name="CatchParameterName"> | |
| <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> | |
| </module> | |
| <module name="LocalVariableName"> | |
| <property name="tokens" value="VARIABLE_DEF"/> | |
| <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> | |
| </module> | |
| <module name="ClassTypeParameterName"> | |
| <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> | |
| </module> | |
| <module name="MethodTypeParameterName"> | |
| <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> | |
| </module> | |
| <module name="InterfaceTypeParameterName"> | |
| <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> | |
| </module> | |
| <module name="NoFinalizer"/> | |
| <module name="GenericWhitespace"/> | |
| <module name="Indentation"> | |
| <property name="basicOffset" value="2"/> | |
| <property name="braceAdjustment" value="0"/> | |
| <property name="caseIndent" value="2"/> | |
| <property name="throwsIndent" value="4"/> | |
| <property name="lineWrappingIndentation" value="4"/> | |
| <property name="arrayInitIndent" value="2"/> | |
| </module> | |
| <module name="AbbreviationAsWordInName"> | |
| <property name="ignoreFinal" value="false"/> | |
| <property name="allowedAbbreviationLength" value="1"/> | |
| </module> | |
| <module name="OverloadMethodsDeclarationOrder"/> | |
| <module name="VariableDeclarationUsageDistance"/> | |
| <module name="MethodParamPad"/> | |
| <module name="NoWhitespaceBefore"> | |
| <property name="tokens" value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/> | |
| <property name="allowLineBreaks" value="true"/> | |
| </module> | |
| <module name="ParenPad"/> | |
| <module name="OperatorWrap"> | |
| <property name="option" value="NL"/> | |
| <property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF"/> | |
| </module> | |
| <module name="AnnotationLocation"> | |
| <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/> | |
| </module> | |
| <module name="AnnotationLocation"> | |
| <property name="tokens" value="VARIABLE_DEF"/> | |
| <property name="allowSamelineMultipleAnnotations" value="true"/> | |
| </module> | |
| <module name="NonEmptyAtclauseDescription"/> | |
| <module name="MethodName"> | |
| <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/> | |
| </module> | |
| <module name="EmptyCatchBlock"> | |
| <property name="exceptionVariableName" value="expected"/> | |
| </module> | |
| <module name="CommentsIndentation"/> | |
| </module> | |
| </module> | |
| EOF | |
| - name: Get changed Java files | |
| id: changed-files | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | |
| HEAD_SHA="${{ github.sha }}" | |
| echo "Comparing $BASE_SHA...$HEAD_SHA" | |
| MERGE_BASE=$(git merge-base $BASE_SHA $HEAD_SHA) | |
| echo "Merge base: $MERGE_BASE" | |
| git diff --name-only --diff-filter=ACMRT $MERGE_BASE $HEAD_SHA | grep '\.java$' > changed_files.txt || true | |
| else | |
| if git rev-parse HEAD^ >/dev/null 2>&1; then | |
| git diff --name-only --diff-filter=ACMRT HEAD^ HEAD | grep '\.java$' > changed_files.txt || true | |
| else | |
| git diff-tree --no-commit-id --name-only --diff-filter=ACMRT -r HEAD | grep '\.java$' > changed_files.txt || true | |
| fi | |
| fi | |
| if [ -s changed_files.txt ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| echo "Changed Java files:" | |
| cat changed_files.txt | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "No Java files changed" | |
| fi | |
| - name: Run Checkstyle | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| run: | | |
| mkdir -p reports | |
| java -jar $CHECKSTYLE_JAR -c checkstyle.xml $(cat changed_files.txt | tr '\n' ' ') -f xml -o reports/checkstyle-result.xml || true | |
| java -jar $CHECKSTYLE_JAR -c checkstyle.xml $(cat changed_files.txt | tr '\n' ' ') || true | |
| - name: Run SpotBugs | |
| if: steps.changed-files.outputs.has_changes == 'true' && steps.detect-build.outputs.build_tool == 'maven' | |
| continue-on-error: true | |
| run: | | |
| mvn clean compile spotbugs:spotbugs || true | |
| - name: Run SpotBugs (Gradle) | |
| if: steps.changed-files.outputs.has_changes == 'true' && steps.detect-build.outputs.build_tool == 'gradle' | |
| continue-on-error: true | |
| run: | | |
| if grep -q "spotbugs" build.gradle* 2>/dev/null; then | |
| ./gradlew spotbugsMain || true | |
| else | |
| echo "SpotBugs not configured in Gradle project" | |
| fi | |
| - name: Format Java files | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| run: | | |
| mkdir -p formatted | |
| while IFS= read -r file; do | |
| if [ -f "$file" ]; then | |
| echo "Formatting $file..." | |
| java -jar $FORMATTER_JAR --replace "$file" | |
| fi | |
| done < changed_files.txt | |
| - name: Generate diff and suggestions | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| id: generate-diff | |
| run: | | |
| mkdir -p suggestions | |
| cat > suggestions/review.md << 'HEADER' | |
| ## 🔍 Java Code Review Results | |
| ### Summary | |
| This automated review checks your Java code for: | |
| - **Style violations** (Checkstyle) | |
| - **Code formatting** (Google Java Format) | |
| - **Best practices** | |
| --- | |
| HEADER | |
| if git diff --quiet; then | |
| echo "no_changes=true" >> $GITHUB_OUTPUT | |
| echo "✅ **All files are properly formatted!**" >> suggestions/review.md | |
| else | |
| echo "no_changes=false" >> $GITHUB_OUTPUT | |
| echo "### 📝 Formatting Changes Required" >> suggestions/review.md | |
| echo "" >> suggestions/review.md | |
| while IFS= read -r file; do | |
| if [ -f "$file" ] && ! git diff --quiet "$file"; then | |
| echo "#### \`$file\`" >> suggestions/review.md | |
| echo "" >> suggestions/review.md | |
| echo "<details>" >> suggestions/review.md | |
| echo "<summary>View suggested changes</summary>" >> suggestions/review.md | |
| echo "" >> suggestions/review.md | |
| echo '```diff' >> suggestions/review.md | |
| git diff "$file" >> suggestions/review.md | |
| echo '```' >> suggestions/review.md | |
| echo "" >> suggestions/review.md | |
| echo "</details>" >> suggestions/review.md | |
| echo "" >> suggestions/review.md | |
| fi | |
| done < changed_files.txt | |
| fi | |
| if [ -f "reports/checkstyle-result.xml" ]; then | |
| ERROR_COUNT=$(grep -o '<error' reports/checkstyle-result.xml | wc -l) | |
| if [ "$ERROR_COUNT" -gt 0 ]; then | |
| echo "" >> suggestions/review.md | |
| echo "### ⚠️ Checkstyle Issues Found: $ERROR_COUNT" >> suggestions/review.md | |
| echo "" >> suggestions/review.md | |
| echo "<details>" >> suggestions/review.md | |
| echo "<summary>View Checkstyle violations</summary>" >> suggestions/review.md | |
| echo "" >> suggestions/review.md | |
| java -jar $CHECKSTYLE_JAR -c checkstyle.xml $(cat changed_files.txt | tr '\n' ' ') 2>&1 | head -100 >> suggestions/review.md || true | |
| echo "" >> suggestions/review.md | |
| echo "</details>" >> suggestions/review.md | |
| fi | |
| fi | |
| cat >> suggestions/review.md << 'FOOTER' | |
| --- | |
| ### 💡 Recommendations | |
| 1. **Apply formatting**: Run `google-java-format` on your files | |
| 2. **Fix Checkstyle issues**: Address the violations mentioned above | |
| 3. **Review best practices**: Ensure your code follows Java conventions | |
| ### 🛠️ How to Fix Locally | |
| ```bash | |
| # Download Google Java Format | |
| wget https://github.com/google/google-java-format/releases/download/v1.19.1/google-java-format-1.19.1-all-deps.jar | |
| # Format your Java files | |
| java -jar google-java-format-1.19.1-all-deps.jar --replace src/**/*.java | |
| ``` | |
| Or use your IDE plugin: | |
| - **IntelliJ IDEA**: Install "google-java-format" plugin | |
| - **Eclipse**: Install "google-java-format" plugin | |
| - **VS Code**: Install "Language Support for Java" extension | |
| FOOTER | |
| - name: Comment PR with review | |
| if: github.event_name == 'pull_request' && steps.changed-files.outputs.has_changes == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const reviewContent = fs.readFileSync('suggestions/review.md', 'utf8'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('🔍 Java Code Review Results') | |
| ); | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: reviewContent | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: reviewContent | |
| }); | |
| } | |
| - name: Upload formatted files as artifact | |
| if: steps.changed-files.outputs.has_changes == 'true' && steps.generate-diff.outputs.no_changes == 'false' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: formatted-java-files | |
| path: | | |
| **/*.java | |
| retention-days: 7 | |
| - name: Upload Checkstyle report | |
| if: steps.changed-files.outputs.has_changes == 'true' && hashFiles('reports/checkstyle-result.xml') != '' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: checkstyle-report | |
| path: reports/checkstyle-result.xml | |
| retention-days: 14 | |
| - name: Create check run | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| let conclusion = 'success'; | |
| let summary = '✅ All checks passed!'; | |
| if (fs.existsSync('reports/checkstyle-result.xml')) { | |
| const content = fs.readFileSync('reports/checkstyle-result.xml', 'utf8'); | |
| const errorCount = (content.match(/<error/g) || []).length; | |
| if (errorCount > 0) { | |
| conclusion = 'failure'; | |
| summary = `❌ Found ${errorCount} Checkstyle violations`; | |
| } | |
| } | |
| if ('${{ steps.generate-diff.outputs.no_changes }}' === 'false') { | |
| conclusion = 'failure'; | |
| summary += '\n❌ Code formatting issues detected'; | |
| } | |
| await github.rest.checks.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: 'Java Lint & Format Check', | |
| head_sha: context.sha, | |
| status: 'completed', | |
| conclusion: conclusion, | |
| output: { | |
| title: 'Java Code Quality Check', | |
| summary: summary | |
| } | |
| }); | |
| - name: Fail if issues found | |
| if: steps.changed-files.outputs.has_changes == 'true' && steps.generate-diff.outputs.no_changes == 'false' | |
| run: | | |
| echo "❌ Code formatting or style issues detected!" | |
| echo "Please review the PR comment for detailed suggestions." | |
| exit 1 |