diff --git a/.default.docker.env b/.default.docker.env new file mode 100644 index 0000000..01ee4f2 --- /dev/null +++ b/.default.docker.env @@ -0,0 +1,5 @@ +# Set default Sonarqube environment variables used by docker-compose +# You can override these envvars by creating a '.override.docker.env' file +# For available envvars list, see https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/configure-and-operate-a-server/environment-variables/ + +SONAR_LOG_LEVEL_WEB=INFO diff --git a/.gitattributes b/.gitattributes index 12b960c..9039a78 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,4 +4,8 @@ # Ensure BAT files will always be checked out with CRLFs (regardless of the # OS they were checked out on). -*.bat text eol=crlf \ No newline at end of file +*.bat text eol=crlf + +# Ensure BAT files will always be checked out with CRLFs (regardless of the +# OS they were checked out on). +*.cmd text eol=crlf diff --git a/.github/workflows/_BACKUP_manual_release.yml b/.github/workflows/_BACKUP_manual_release.yml index 90818a5..0930754 100644 --- a/.github/workflows/_BACKUP_manual_release.yml +++ b/.github/workflows/_BACKUP_manual_release.yml @@ -31,9 +31,9 @@ jobs: git config user.name 'github-actions[bot]' git config user.email '' - name: Maven release - run: mvn release:prepare -B -ff -DtagNameFormat=@{project.version} + run: ./mvnw release:prepare -B -ff -DtagNameFormat=@{project.version} - name: Maven release clean - run: mvn release:clean + run: ./mvnw release:clean - name: Get last TAG run: echo "LAST_TAG=$(git tag --sort=-version:refname | head -n 1)" >> $GITHUB_ENV - name: Extract release notes @@ -44,7 +44,7 @@ jobs: with: ref: ${{ env.LAST_TAG }} - name: Build project - run: mvn -e -B clean package -DskipTests + run: ./mvnw -e -B clean package -DskipTests - name: Create release id: create_release uses: actions/create-release@v1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60db5ba..b6e2d7e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: restore-keys: ${{ runner.os }}-m2 - name: Verify - run: mvn -e -B verify + run: ./mvnw -e -B verify - name: Set up JDK 17 uses: actions/setup-java@v3 @@ -57,4 +57,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -e -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=green-code-initiative_ecoCode-java + run: ./mvnw -e -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=green-code-initiative_ecoCode-java diff --git a/.github/workflows/tag_release.yml b/.github/workflows/tag_release.yml index b405ace..75e6e62 100644 --- a/.github/workflows/tag_release.yml +++ b/.github/workflows/tag_release.yml @@ -29,7 +29,7 @@ jobs: id: extract-release-notes uses: ffurrer2/extract-release-notes@v1 - name: Build project - run: mvn -e -B clean package -DskipTests + run: ./mvnw -e -B clean package -DskipTests - name: Create release id: create_release uses: actions/create-release@v1 diff --git a/.gitignore b/.gitignore index 67cc592..8554f83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Ignore all files and folders starting with ".", except a few exceptions .* +!.mvn/ !.gitignore !.gitattributes !.github/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# 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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce62a8..9bec331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- [#59](https://github.com/green-code-initiative/ecoCode-java/pull/59) Add builtin profile `ecoCode way` to aggregate all implemented ecoCode rules by this plugin + ### Changed +- [#49](https://github.com/green-code-initiative/ecoCode-java/pull/49) Add test to ensure all Rules are registered +- [#336](https://github.com/green-code-initiative/ecoCode/issues/336) [Adds Maven Wrapper](https://github.com/green-code-initiative/ecoCode-java/pull/67) + ### Deleted +## [1.6.2] - 2024-07-21 + +### Changed + +- [#60](https://github.com/green-code-initiative/ecoCode-java/issues/60) Check + update for SonarQube 10.6.0 compatibility +- refactoring docker system +- upgrade ecocode-rules-specifications to 1.6.2 + ## [1.6.1] - 2024-05-15 ### Changed diff --git a/Dockerfile b/Dockerfile index a5a0fc7..421eacd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,16 @@ -FROM maven:3-openjdk-11-slim AS builder +ARG MAVEN_BUILDER=3-openjdk-17-slim +ARG SONARQUBE_VERSION=10.6.0-community + +FROM maven:${MAVEN_BUILDER} AS builder COPY . /usr/src/ecocode WORKDIR /usr/src/ecocode +COPY src src/ +COPY pom.xml tool_build.sh ./ + RUN ./tool_build.sh -FROM sonarqube:10.5.1-community +FROM sonarqube:${SONARQUBE_VERSION} COPY --from=builder /usr/src/ecocode/target/ecocode-*.jar /opt/sonarqube/extensions/plugins/ +USER sonarqube diff --git a/README.md b/README.md index 7fab3c0..a28c058 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Ready to use binaries are available [from GitHub](https://github.com/green-code- | Plugin version | SonarQube version | Java version | |----------------|---------------------|--------------| -| 1.5.+ | 9.4.+ LTS to 10.5.1 | 11 / 17 | +| 1.6.+ | 9.4.+ LTS to 10.6.0 | 11 / 17 | > Compatibility table of versions lower than 1.4.+ are available from the > main [ecoCode repository](https://github.com/green-code-initiative/ecoCode#-plugins-version-compatibility). diff --git a/_TODOs_DDC.md b/_TODOs_DDC.md index d318d99..85e767d 100644 --- a/_TODOs_DDC.md +++ b/_TODOs_DDC.md @@ -7,4 +7,5 @@ - check usefulness - upgrade versions - enable github `dependabot` to create automatically PR with version upgrades of dependencides (when all dependencies will be ok) -- ménage dans les branches de dev (local et remote) \ No newline at end of file +- ménage dans les branches de dev (local et remote) +- docker-compose : ":9000" (génération port aléatoire pour l'IHM + repérage pour IHM) au lieu de "9000:9000" si erreur lors du démarrage "Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:9000 -> 0.0.0.0:0: listen tcp 0.0.0.0:9000: bind: address already in use" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ee7dc87..e84e389 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,23 +1,27 @@ -version: "3.3" +name: sonarqube_ecocode_java + services: sonar: - image: sonarqube:10.5.1-community + build: . container_name: sonar_ecocode_java ports: - - "9000:9000" + - ":9000" networks: - sonarnet depends_on: - - db + db: + condition: service_healthy environment: SONAR_JDBC_USERNAME: sonar SONAR_JDBC_PASSWORD: sonar SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonarqube SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: 'true' + env_file: + - path: ./.default.docker.env + required: true + - path: ./.override.docker.env + required: false volumes: - - type: bind - source: ./target/ecocode-java-plugin-1.6.2-SNAPSHOT.jar - target: /opt/sonarqube/extensions/plugins/ecocode-java-plugin-1.6.2-SNAPSHOT.jar - "extensions:/opt/sonarqube/extensions" - "logs:/opt/sonarqube/logs" - "data:/opt/sonarqube/data" @@ -34,6 +38,11 @@ services: POSTGRES_PASSWORD: sonar POSTGRES_DB: sonarqube PGDATA: pg_data:/var/lib/postgresql/data/pgdata + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U sonar -d sonarqube" ] + interval: 5s + timeout: 5s + retries: 5 networks: sonarnet: diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..249bdf3 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index fa0c7e9..9283617 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.ecocode ecocode-java-plugin - 1.6.2-SNAPSHOT + 1.6.3-SNAPSHOT sonar-plugin @@ -67,7 +67,7 @@ 1.7 - 1.5.1 + 1.6.2 @@ -136,6 +136,12 @@ test + + org.reflections + reflections + 0.10.2 + test + diff --git a/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java b/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java index f4ef343..b90a262 100644 --- a/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java +++ b/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java @@ -35,6 +35,7 @@ import fr.greencodeinitiative.java.checks.InitializeBufferWithAppropriateSize; import fr.greencodeinitiative.java.checks.NoFunctionCallWhenDeclaringForLoop; import fr.greencodeinitiative.java.checks.OptimizeReadFileExceptions; +import fr.greencodeinitiative.java.checks.UseEveryColumnQueried; import org.sonar.plugins.java.api.CheckRegistrar; import org.sonar.plugins.java.api.JavaCheck; import org.sonarsource.api.sonarlint.SonarLintSide; @@ -47,7 +48,7 @@ */ @SonarLintSide public class JavaCheckRegistrar implements CheckRegistrar { - private static final List> ANNOTATED_RULE_CLASSES = List.of( + static final List> ANNOTATED_RULE_CLASSES = List.of( ArrayCopyCheck.class, IncrementCheck.class, AvoidUsageOfStaticCollections.class, @@ -62,7 +63,8 @@ public class JavaCheckRegistrar implements CheckRegistrar { InitializeBufferWithAppropriateSize.class, AvoidSetConstantInBatchUpdate.class, FreeResourcesOfAutoCloseableInterface.class, - AvoidMultipleIfElseStatement.class + AvoidMultipleIfElseStatement.class, + UseEveryColumnQueried.class ); /** diff --git a/src/main/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfile.java b/src/main/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfile.java new file mode 100644 index 0000000..d24fc82 --- /dev/null +++ b/src/main/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfile.java @@ -0,0 +1,40 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java; + +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader; + +import static fr.greencodeinitiative.java.JavaRulesDefinition.LANGUAGE; +import static fr.greencodeinitiative.java.JavaRulesDefinition.REPOSITORY_KEY; + +public final class JavaEcoCodeWayProfile implements BuiltInQualityProfilesDefinition { + static final String PROFILE_NAME = "ecoCode way"; + static final String PROFILE_PATH = JavaEcoCodeWayProfile.class.getPackageName().replace('.', '/') + "/ecoCode_way_profile.json"; + + @Override + public void define(Context context) { + NewBuiltInQualityProfile ecoCodeProfile = context.createBuiltInQualityProfile(PROFILE_NAME, LANGUAGE); + loadProfile(ecoCodeProfile); + ecoCodeProfile.done(); + } + + private void loadProfile(NewBuiltInQualityProfile profile) { + BuiltInQualityProfileJsonLoader.load(profile, REPOSITORY_KEY, PROFILE_PATH); + } +} diff --git a/src/main/java/fr/greencodeinitiative/java/JavaRulesDefinition.java b/src/main/java/fr/greencodeinitiative/java/JavaRulesDefinition.java index 958edc8..f9e03e3 100644 --- a/src/main/java/fr/greencodeinitiative/java/JavaRulesDefinition.java +++ b/src/main/java/fr/greencodeinitiative/java/JavaRulesDefinition.java @@ -31,7 +31,7 @@ public class JavaRulesDefinition implements RulesDefinition { private static final String RESOURCE_BASE_PATH = "io/ecocode/rules/java"; private static final String NAME = "ecoCode"; - private static final String LANGUAGE = "java"; + static final String LANGUAGE = "java"; static final String REPOSITORY_KEY = "ecocode-java"; private final SonarRuntime sonarRuntime; diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java new file mode 100644 index 0000000..7e85e11 --- /dev/null +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -0,0 +1,357 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol; +import org.sonar.plugins.java.api.tree.Arguments; +import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.LiteralTree; +import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.VariableTree; +import org.sonar.plugins.java.api.tree.Tree.Kind; + +@Rule(key = "EC1044") +public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { + + protected static final String MESSAGERULE = "Avoid querying SQL columns that are not used"; + private static final String JAVA_SQL_STATEMENT = "java.sql.Statement"; + private static final String JAVA_SQL_RESULTSET = "java.sql.ResultSet"; + private static final MethodMatchers SQL_STATEMENT_DECLARE_SQL = MethodMatchers.create() + .ofSubTypes(JAVA_SQL_STATEMENT) + .names("executeQuery", "execute") + .addParametersMatcher("java.lang.String") + .build(); + private static final MethodMatchers SQL_STATEMENT_RETRIEVE_RESULTSET = MethodMatchers.create() + .ofSubTypes(JAVA_SQL_STATEMENT) + .names("executeQuery", "getResultSet") + .withAnyParameters() + .build(); + private static final MethodMatchers SQL_RESULTSET_GET_COLNAME = MethodMatchers.create() + .ofSubTypes(JAVA_SQL_RESULTSET) + .name(n -> n.startsWith("get")) + .addParametersMatcher("java.lang.String") + .build(); + private static final MethodMatchers SQL_RESULTSET_GET_COLID = MethodMatchers.create() + .ofSubTypes(JAVA_SQL_RESULTSET) + .name(n -> n.startsWith("get")) + .addParametersMatcher("int") + .build(); + private static final Pattern SELECTED_COLUMNS_PATTERN = Pattern.compile("SELECT\\s+(.*)\\s+FROM\\s+.*"); + + @Override + public List nodesToVisit() { + return Arrays.asList(Kind.METHOD_INVOCATION); + } + + /** + * How this rule works : + * We start from the method invocation that declares the SQL query ( stmt.executeQuery("SELECT ... FROM ...") ) + * the selected columns are directly extracted from the parameters of this method invocation + * We explore the stmt object to find where the method invocation that returns the ResultSet object + * finally we explore all invocations of this ResultSet object to list all the column used. + * the selected and used columns are compared, and an issue is reported if columns are selected but not used. + * + * + * stmt.execute("SELECT ... FROM ...") or stmt.executeQuery(...) + * | | + * | ----> Selected Columns + * [Statement Object] + * | + * | + * v + * res = stmt.getResultSet() or stmt.executeQuery(...) + * | + * | + * [ResultSet Object] + * | + * | + * v + * res.getInt(...) or any column extraction method + * | + * ----> Used Column + * + */ + @Override + public void visitNode(Tree tree) { + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; + if (!SQL_STATEMENT_DECLARE_SQL.matches(methodInvocationTree)) { + return; + } + + // extraction of the selected columns + List selectedColumns = getSelectedColumns(methodInvocationTree); + if (selectedColumns.isEmpty()) { + return; + } + + //if selected columns includes "*", stop the search + if (selectedColumns.contains("*")) { + return; + } + + // get the ResultSet object and check it's validity + Symbol resultSet = getResultSetNode(methodInvocationTree); + if (resultSet == null) { + return; + } + if(isResultSetInvalid(resultSet)){ + return; + } + + // extraction of the used columns + List usedColumns = getUsedColumns(resultSet, selectedColumns); + + // if there are selected columns that are not used in the code, report the issue + List differences = selectedColumns.stream() + .filter(element -> !usedColumns.contains(element)) + .collect(Collectors.toList()); + if (!differences.isEmpty()) { + reportIssue(methodInvocationTree, MESSAGERULE); + } + } + + private static List getSelectedColumns(MethodInvocationTree methodInvocationTree) { + // get the first argument of the query definition method + Arguments arguments = methodInvocationTree.arguments(); + if (arguments.isEmpty()) { + return new ArrayList<>(); + } + ExpressionTree argument = arguments.get(0); + // get the contents of the string in this first parameters + LiteralTree literal = extractLiteralFromVariable(argument); + if (literal == null) { + return new ArrayList<>(); + } + String query = literal.value(); + //get the list of selected columns from this string + return extractSelectedSQLColumns(query); + } + + /** + * returns a list of used columns from a resultset object and a list of the selected columns + */ + private static List getUsedColumns(Symbol resultSet, List selectedColumns) { + // iterate across all usages of the ResultSet + List usedColumns = new ArrayList<>(); + List resultSetUsages = resultSet.usages(); + for (IdentifierTree usage : resultSetUsages) { + // check this usage is an assignement, and the method parameters + if (usage.parent().is(Tree.Kind.ASSIGNMENT)) { + break; + } + MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); + if (methodInvocation == null || methodInvocation.arguments().isEmpty()) { + continue; + } + // get the value of the first parameter + ExpressionTree parameter = methodInvocation.arguments().get(0); + LiteralTree columnGot = extractLiteralFromVariable(parameter); + if (columnGot == null) { + continue; + } + String column; + String value = columnGot.value(); + // if this first parameter is a string, clean up and use as is for used column name + if (SQL_RESULTSET_GET_COLNAME.matches(methodInvocation) && columnGot.is(Tree.Kind.STRING_LITERAL)) { + column = value.toUpperCase() + .replaceAll("^['\"]", "") + .replaceAll("['\"]$", ""); + // if this first parameter is an int, use as and id for used column name + } else if (SQL_RESULTSET_GET_COLID.matches(methodInvocation) && columnGot.is(Tree.Kind.INT_LITERAL)) { + int columnId = Integer.parseInt(value); + if (columnId > selectedColumns.size()) { + break; + } + column = selectedColumns.get(columnId - 1); + } else { + continue; + } + usedColumns.add(column); + } + return usedColumns; + } + + /** + * returns the litteral assigned to a variable var + * var has to be either a litteral itself or a final variable + * returns null if the ExpressionTree is not a variable, + * if the variable is not final, or if the variable has not been initialized + */ + @Nullable + private static LiteralTree extractLiteralFromVariable(ExpressionTree tree) { + if (tree instanceof LiteralTree) { + return (LiteralTree) tree; + } + if (!tree.is(Tree.Kind.IDENTIFIER)) { + return null; + } + IdentifierTree identifierTree = (IdentifierTree) tree; + Symbol symbol = identifierTree.symbol(); + if (symbol == null || !symbol.isFinal() || !symbol.isVariableSymbol()) { + return null; + } + VariableSymbol variableSymbol = (VariableSymbol) symbol; + Tree assignment = variableSymbol.declaration(); + if (!assignment.is(Tree.Kind.VARIABLE)) { + return null; + } + VariableTree variableTree = (VariableTree) assignment; + ExpressionTree initializer = variableTree.initializer(); + if (initializer instanceof LiteralTree) { + return (LiteralTree) initializer; + } + return null; + } + + /** + * get the ResultSet Object assigned from the result of the retrieve resultset method + * from the sql declaration method (via the shared stmt object) + * stmt.execute(...) -> rs = stmt.getResultSet() + */ + @Nullable + private static Symbol getResultSetNode(MethodInvocationTree methodInvocationTree) { + // get the Statement object on witch the method is called + ExpressionTree et = methodInvocationTree.methodSelect(); + if (!et.is(Tree.Kind.MEMBER_SELECT)) { + return null; + } + MemberSelectExpressionTree mset = (MemberSelectExpressionTree) et; + ExpressionTree expression = mset.expression(); + if (!expression.is(Tree.Kind.IDENTIFIER)) { + return null; + } + IdentifierTree id = (IdentifierTree) expression; + Symbol statement = id.symbol(); + if (statement == null) { + return null; + } + // iterate over all usages of this Statement object + List usages = statement.usages(); + Symbol resultSet = null; + for (IdentifierTree usage : usages) { + // does this usage of the Statement object match SQL_STATEMENT_RETRIEVE_RESULTSET ? + MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); + if (methodInvocation == null || !SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)) { + continue; + } + // if so end the search, we have found our resultSet object + Tree parent = methodInvocation.parent(); + if (parent.is(Tree.Kind.VARIABLE)) { + resultSet = ((VariableTree) parent).symbol(); + break; + } + } + return resultSet; + } + + /** + * unpacks a chain call to get the method invocation node + * example : this.object.chain.method() -> method() + */ + @Nullable + private static MethodInvocationTree getMethodInvocationFromTree(IdentifierTree tree) { + Tree parent = tree; + while (parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION)) { + parent = parent.parent(); + } + return (MethodInvocationTree) parent; + } + + /** + * checks the two conditions that make a ResultSet object invalid, + * and would stop the search for used columns because of side effects + * - the ResultSet object being passed in a method + * - the ResultSet object being reassigned + */ + private static boolean isResultSetInvalid(Symbol resultSet) { + return isObjectUsedInMethodParameters(resultSet) + || isObjectReassigned(resultSet); + } + + /** + * checks if an object is used as a parameter a method + */ + private static boolean isObjectUsedInMethodParameters(Symbol obj) { + List usages = obj.usages(); + for (IdentifierTree usage : usages) { + Tree parent = usage.parent(); + if (parent.is(Tree.Kind.ARGUMENTS)) { + return true; + } + } + return false; + } + + /** + * checks if an object is reassigned + */ + private static boolean isObjectReassigned(Symbol obj) { + List usages = obj.usages(); + for (IdentifierTree usage : usages) { + Tree parent = usage.parent(); + if (parent.is(Tree.Kind.ASSIGNMENT)) { + AssignmentExpressionTree assignment = (AssignmentExpressionTree) parent; + ExpressionTree expressionTree = assignment.variable(); + if (expressionTree.is(Tree.Kind.IDENTIFIER) + && obj.equals(((IdentifierTree) expressionTree).symbol())) { + return true; + } + } + } + return false; + } + + /** + * extract from a SQL query in the form of "SELECT X, Y AS Z FROM TABLE ..." + * a list of all the column names and aliases (X and Z) without whitespace and in uppercase + */ + static List extractSelectedSQLColumns(String query) { + if (query == null) { + return new ArrayList<>(); + } + query = query.toUpperCase() + .replaceAll("^['\"]", "") + .replaceAll("['\"]$", ""); + List columns = new ArrayList<>(); + Matcher matcher = SELECTED_COLUMNS_PATTERN.matcher(query); + if (matcher.matches()) { + String columnString = matcher.group(1); + columns = Arrays.asList(columnString.split(",")); + columns.replaceAll(column -> column.replaceAll("\\s+", " ")); + columns.replaceAll(column -> column.contains(" AS ") ? column.split(" AS ")[1].trim() : column.trim()); + } + return columns; + } +} diff --git a/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json b/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json new file mode 100644 index 0000000..b2f127c --- /dev/null +++ b/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json @@ -0,0 +1,22 @@ +{ + "name": "ecoCode way", + "language": "java", + "ruleKeys": [ + "EC1", + "EC2", + "EC3", + "EC5", + "EC27", + "EC28", + "EC32", + "EC67", + "EC69", + "EC72", + "EC74", + "EC76", + "EC77", + "EC78", + "EC79", + "EC1044" + ] +} diff --git a/src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java b/src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java new file mode 100644 index 0000000..c12e375 --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java @@ -0,0 +1,54 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * This is the nominal test case, where the SQL query is an attribute of the class. + * All Fields are accessed, so no issue is raised + */ +public class AttributeQueryCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt("age")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java b/src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java new file mode 100644 index 0000000..e42ff48 --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java @@ -0,0 +1,53 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * This is the nominal test case, where the SQL query is an attribute of the class. + * One field is not accesed, so an issue is raised + */ +public class AttributeQueryNonCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}} + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java b/src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java new file mode 100644 index 0000000..6146dfa --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java @@ -0,0 +1,55 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, the query is a litteral string directly inserted in the method + * All Fields are accessed, so no issue is raised + */ +public class LitteralQueryCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt("age")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + +} diff --git a/src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java b/src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java new file mode 100644 index 0000000..476233d --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java @@ -0,0 +1,54 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, the query is a litteral string directly inserted in the method + * One field is not accesed, so an issue is raised + */ +public class LitteralQueryNonCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { // Noncompliant {{Avoid querying SQL columns that are not used}} + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + +} \ No newline at end of file diff --git a/src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java b/src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java new file mode 100644 index 0000000..94f7a29 --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java @@ -0,0 +1,69 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, multiple queries are done using the same Statement Object. + * All Fields are accessed, so no issue is raised + */ +public class MultipleQueriesCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + private static final String QUERY2 = "SELECT id, first, last FROM Registration2"; + + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ) { + + ResultSet rs = stmt.executeQuery(QUERY); + while (rs.next()) { + // Display values + System.out.print("Age: " + rs.getInt("age")); + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + rs = stmt.executeQuery(QUERY2); + + + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + + + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java b/src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java new file mode 100644 index 0000000..f7cf12c --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java @@ -0,0 +1,68 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, multiple queries are done using the same Statement Object. + * One field is not accesed, so an issue is raised + */ +public class MultipleQueriesNonCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + private static final String QUERY2 = "SELECT id, first, last, age FROM Registration2"; + + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ) { + + ResultSet rs = stmt.executeQuery(QUERY); // Noncompliant {{Avoid querying SQL columns that are not used}} + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + rs = stmt.executeQuery(QUERY2); + + while (rs.next()) { + // Display values + System.out.print("Age: " + rs.getInt("age")); + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + + + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueried/SelectStar.java b/src/test/files/UseEveryColumnQueried/SelectStar.java new file mode 100644 index 0000000..84fe348 --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/SelectStar.java @@ -0,0 +1,55 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, we use a "select * from" statement + * Since we can't know what columns exist, we can't know if all columns are being used, no issue is raised + * "select * from" is bad practice but is already covered by EC74 + */ +public class SelectStar { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT * FROM Registration"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt("age")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java new file mode 100644 index 0000000..03ac224 --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java @@ -0,0 +1,56 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, columns are accesed by IDs and names, some of them being in final variables + * All Fields are accessed, so no issue is raised + */ +public class UseColumnIdsAndNameAttributesCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + private static final String ID = "id"; + private static final int AGE = 4; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt(ID)); + System.out.print(", Age: " + rs.getInt(AGE)); + System.out.print(", First: " + rs.getString(2)); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java new file mode 100644 index 0000000..c7972d4 --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java @@ -0,0 +1,54 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, columns are accesed by IDs and names, some of them being in final variables + * One field is not accesed, so an issue is raised + */ +public class UseColumnIdsAndNameAttributesNonCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + private static final String ID = "id"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}} + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt(ID)); + System.out.print(", First: " + rs.getString(2)); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueried/UseMethodCompliant.java b/src/test/files/UseEveryColumnQueried/UseMethodCompliant.java new file mode 100644 index 0000000..db7ae3e --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/UseMethodCompliant.java @@ -0,0 +1,57 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, the ResultSet is passed through a method + * All Fields are accessed, so no issue is raised + */ +public class UseMethodCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { + extractGet(rs); + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + private void extractGet(ResultSet rs) throws SQLException { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt("age")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } +} diff --git a/src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java b/src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java new file mode 100644 index 0000000..559c06b --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java @@ -0,0 +1,56 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, the ResultSet is passed through a method + * One field is not accesed, so an issue is raised + */ +public class UseMethodNonCompliant { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { // Noncompliant {{Avoid querying SQL columns that are not used}} + extractGet(rs); + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + private void extractGet(ResultSet rs) throws SQLException { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } +} diff --git a/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java b/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java index 02270ca..09655b6 100644 --- a/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java +++ b/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java @@ -17,7 +17,11 @@ */ package fr.greencodeinitiative.java; +import java.util.Set; + import org.junit.jupiter.api.Test; +import org.reflections.Reflections; +import org.sonar.check.Rule; import org.sonar.plugins.java.api.CheckRegistrar; import static org.assertj.core.api.Assertions.assertThat; @@ -30,10 +34,14 @@ void checkNumberRules() { final JavaCheckRegistrar registrar = new JavaCheckRegistrar(); registrar.register(context); - - assertThat(context.checkClasses()).hasSize(15); + assertThat(context.checkClasses()) + .describedAs("All implemented rules must be registered into " + JavaCheckRegistrar.class) + .containsExactlyInAnyOrder(getDefinedRules().toArray(new Class[0])); assertThat(context.testCheckClasses()).isEmpty(); - } + static Set> getDefinedRules() { + Reflections r = new Reflections(JavaCheckRegistrar.class.getPackageName() + ".checks"); + return r.getTypesAnnotatedWith(Rule.class); + } } diff --git a/src/test/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfileTest.java b/src/test/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfileTest.java new file mode 100644 index 0000000..0d75016 --- /dev/null +++ b/src/test/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfileTest.java @@ -0,0 +1,51 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.check.Rule; + +import static fr.greencodeinitiative.java.JavaCheckRegistrarTest.getDefinedRules; +import static fr.greencodeinitiative.java.JavaEcoCodeWayProfile.PROFILE_NAME; +import static fr.greencodeinitiative.java.JavaEcoCodeWayProfile.PROFILE_PATH; +import static fr.greencodeinitiative.java.JavaRulesDefinition.LANGUAGE; +import static org.assertj.core.api.Assertions.assertThat; + +class JavaEcoCodeWayProfileTest { + @Test + void should_create_ecocode_profile() { + BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); + + JavaEcoCodeWayProfile definition = new JavaEcoCodeWayProfile(); + definition.define(context); + + BuiltInQualityProfilesDefinition.BuiltInQualityProfile profile = context.profile(LANGUAGE, PROFILE_NAME); + + assertThat(profile.language()).isEqualTo(LANGUAGE); + assertThat(profile.name()).isEqualTo(PROFILE_NAME); + List definedRuleIds = getDefinedRules().stream().map(c -> c.getAnnotation(Rule.class).key()).collect(Collectors.toList()); + assertThat(profile.rules()) + .describedAs("All implemented rules must be declared in '%s' profile file: %s", PROFILE_NAME, PROFILE_PATH) + .map(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) + .containsExactlyInAnyOrderElementsOf(definedRuleIds); + } +} diff --git a/src/test/java/fr/greencodeinitiative/java/JavaRulesDefinitionTest.java b/src/test/java/fr/greencodeinitiative/java/JavaRulesDefinitionTest.java index ad1b536..ee6dff5 100644 --- a/src/test/java/fr/greencodeinitiative/java/JavaRulesDefinitionTest.java +++ b/src/test/java/fr/greencodeinitiative/java/JavaRulesDefinitionTest.java @@ -28,6 +28,7 @@ import org.sonar.api.server.rule.RulesDefinition.Rule; import org.sonar.api.utils.Version; +import static fr.greencodeinitiative.java.JavaCheckRegistrar.ANNOTATED_RULE_CLASSES; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -36,8 +37,6 @@ class JavaRulesDefinitionTest { private RulesDefinition.Repository repository; - private int rulesSize; - @BeforeEach void init() { final SonarRuntime sonarRuntime = mock(SonarRuntime.class); @@ -46,7 +45,6 @@ void init() { RulesDefinition.Context context = new RulesDefinition.Context(); rulesDefinition.define(context); repository = context.repository(rulesDefinition.repositoryKey()); - rulesSize = 15; } @Test @@ -55,12 +53,11 @@ void testMetadata() { assertThat(repository.name()).isEqualTo("ecoCode"); assertThat(repository.language()).isEqualTo("java"); assertThat(repository.key()).isEqualTo("ecocode-java"); - assertThat(repository.rules()).hasSize(rulesSize); } @Test void testRegistredRules() { - assertThat(repository.rules()).hasSize(rulesSize); + assertThat(repository.rules()).hasSize(ANNOTATED_RULE_CLASSES.size()); } @Test diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java new file mode 100644 index 0000000..edf2b76 --- /dev/null +++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java @@ -0,0 +1,131 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package fr.greencodeinitiative.java.checks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +class UseEveryColumnQueriedTest { + + @Test + void testExtractSelectedSQLColumns(){ + String query = "\"SELECT id AS registration_id,\tfirst, last as Final, AGE FROM Registration\""; + List columns = UseEveryColumnQueried.extractSelectedSQLColumns(query); + assertEquals(4, columns.size()); + assertEquals("REGISTRATION_ID", columns.get(0)); + assertEquals("FIRST", columns.get(1)); + assertEquals("FINAL", columns.get(2)); + assertEquals("AGE", columns.get(3)); + } + + @Test + void testHasIssues1() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + void testHasIssues2() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + void testHasIssues3() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + @Disabled // case not handled (multiple queries with the same ResultSet object) + void testHasIssues4() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + @Disabled // case not handled (usage of a method) + void testHasIssues5() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + + @Test + void testHasNoIssues1() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues2() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues3() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues4() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues5() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/UseMethodCompliant.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues6() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueried/SelectStar.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } +} diff --git a/tool_build.sh b/tool_build.sh index bfac031..a5f6a09 100755 --- a/tool_build.sh +++ b/tool_build.sh @@ -1,3 +1,3 @@ #!/usr/bin/env sh -mvn clean package -DskipTests +./mvnw clean package -DskipTests diff --git a/tool_compile.sh b/tool_compile.sh index 3d3e0d8..9d4da3c 100755 --- a/tool_compile.sh +++ b/tool_compile.sh @@ -1,3 +1,3 @@ #!/usr/bin/env sh -mvn clean compile +./mvnw clean compile diff --git a/tool_release_1_prepare.sh b/tool_release_1_prepare.sh index e87b946..7b996e3 100755 --- a/tool_release_1_prepare.sh +++ b/tool_release_1_prepare.sh @@ -5,9 +5,9 @@ ### # creation of 2 commits with release and next SNAPSHOT -mvn release:prepare -B -ff -DpushChanges=false -DtagNameFormat=@{project.version} +./mvnw release:prepare -B -ff -DpushChanges=false -DtagNameFormat=@{project.version} sleep 2 # clean temporary files -mvn release:clean +./mvnw release:clean