diff --git a/.circleci/config.yml b/.circleci/config.yml index 918ae51..6aa66f4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,3 +51,4 @@ workflows: notify: webhooks: - url: https://cc-slack-proxy.herokuapp.com/circle + diff --git a/Dockerfile b/Dockerfile index add05c9..abadc57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:8-jdk-alpine +FROM openjdk:17-jdk-alpine3.14 MAINTAINER Code Climate @@ -6,9 +6,9 @@ RUN adduser -u 9000 -D app VOLUME /code # Increase Java memory limits -ENV JAVA_OPTS="-XX:+UseParNewGC -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -Xss4096k" +ENV JAVA_OPTS="-XX:+UseG1GC -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -Xss4096k" -ENV GRADLE_VERSION=4.2.1 +ENV GRADLE_VERSION=7.3 ENV GRADLE_HOME=/opt/gradle ENV GRADLE_FOLDER=$GRADLE_HOME ENV GRADLE_USER_HOME=$GRADLE_HOME diff --git a/Makefile b/Makefile index 5726ed4..d08c0a3 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,9 @@ endif image: docker build --rm -t $(IMAGE_NAME) . +analyze-fixtures: + docker run --rm -v "$(PWD)/fixtures/java_lib/main/java":/code -v "$(PWD)/fixtures/java_source_version/config_15.json":/config.json $(IMAGE_NAME) + test: image docker run --rm -ti -w /usr/src/app -u root $(IMAGE_NAME) gradle clean test diff --git a/README.md b/README.md index 4f707bd..a30c216 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,31 @@ -# Code Climate Sonar-Java Engine +# Try Qlty today, the newest edition of Code Climate Quality. +#### This repository is deprecated and archived. -[![Maintainability](https://api.codeclimate.com/v1/badges/1ad407dbd79378cf4b07/maintainability)](https://codeclimate.com/repos/59e0f09e141c6104f9000002/maintainability) -[![Test Coverage](https://api.codeclimate.com/v1/badges/1ad407dbd79378cf4b07/test_coverage)](https://codeclimate.com/repos/59e0f09e141c6104f9000002/test_coverage) -[![CircleCI](https://circleci.com/gh/codeclimate/codeclimate-sonar-java.svg?style=svg&circle-token=b800791f4e3af9079991ef70f3871f0ce09ccc81)](https://circleci.com/gh/codeclimate/codeclimate-sonar-java) +This is a repository for a Code Climate Quality plugin which is packaged as a Docker image. -`codeclimate-sonar-java` is a Code Climate engine that wraps [Sonarlint](http://www.sonarlint.org) in standalone mode. +Code Climate Quality is being replaced with the new [Qlty](qlty.sh) code quality platform. Qlty uses a new plugin system which does not require packaging plugins as Docker images. -## Installation -``` -make image -``` +As a result, this repository is no longer maintained and has been archived. -## Tests -``` -make test -``` +## Advantages of Qlty plugins +The new Qlty plugins system provides key advantages over the older, Docker-based plugin system: -## Usage +- Linting runs much faster without the overhead of virtualization +- New versions of linters are available immediately without needing to wait for a re-packaged release +- Plugins can be run with any arbitrary extensions (like extra rules and configs) without requiring pre-packaging +- Eliminates security issues associated with exposing a Docker daemon -1. If you haven't already, [install the Code Climate CLI](https://github.com/codeclimate/codeclimate). -2. Configure a `.codeclimate.yml` file in your repo. -```yml -engines: - sonar-java: - enabled: true - config: - sonar.java.source: 7 - tests_patterns: - - src/test/** -exclude_paths: - - build/ -``` -3. Run `codeclimate analyze`. +## Try out Qlty today free -## Custom configurations +[Qlty CLI](https://docs.qlty.sh/cli/quickstart) is the fastest linter and auto-formatter for polyglot teams. It is completely free and available for Mac, Windows, and Linux. -### Java source version -It is possible to specifcy a Java version the code should be compliant to, it helps Sonar to use the proper rules. -``` -engines: - sonar-java: - enabled: true - config: - sonar.java.source: 7 -``` + - Install Qlty CLI: +` +curl https://qlty.sh | sh # Mac or Linux +` +or ` powershell -c "iwr https://qlty.sh | iex" # Windows` -### Tests -Specifying where the test classes are helps Sonar to use specific rules for those files. -``` -engines: - sonar-java: - enabled: true - config: - tests_patterns: - - src/test/** - - app/src/test/** -``` +[Qlty Cloud](https://docs.qlty.sh/cloud/quickstart) is a full code health platform for integrating code quality into development team workflows. It is free for unlimited private contributors. + - [Try Qlty Cloud today](https://docs.qlty.sh/cloud/quickstart) -### Severity -Ignore issues with severity below the minimum: -``` -engines: - sonar-java: - enabled: true - config: - minimum_severity: critical # default: major - # valid values are: info, minor, major, critical, blocker -``` - -## Sonar Documentation - -http://www.sonarlint.org/commandline - -http://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner - -Issue Tracker: http://jira.sonarsource.com/browse/SLCLI - -## Copyright - -This engine is developed by Code Climate using [SonarLint](http://www.sonarlint.org/commandline), it is not endorsed by SonarSoruce. - -See [LICENSE](LICENSE) +**Note**: For existing customers of Quality, please see our [Migration Guide](https://docs.qlty.sh/migration/guide) for more information and resources. diff --git a/bin/codeclimate-sonar b/bin/codeclimate-sonar index da4093d..7c016e9 100755 --- a/bin/codeclimate-sonar +++ b/bin/codeclimate-sonar @@ -10,7 +10,6 @@ CODE_DIR=$1; shift CONFIG_FILE=$1; shift java \ - -noverify \ -cp ${APP}:${WRAPPER}:${CORE}:${LIBS} \ -Djava.awt.headless=true \ -Dsonarlint.home="${BUILD_DIR}" \ diff --git a/build.gradle b/build.gradle index ad83fb5..6ddc7f2 100644 --- a/build.gradle +++ b/build.gradle @@ -7,18 +7,18 @@ repositories { task copyLibs(type: Copy) { into "${buildDir}/libs" - from configurations.runtime + from configurations.runtimeClasspath exclude "sonar-*-plugin*.jar" } task copyTestLibs(type: Copy) { into "${buildDir}/test" - from configurations.testCompile + from configurations.testRuntimeClasspath } task copyPlugins(type: Copy) { into "${buildDir}/plugins" - from configurations.runtime + from configurations.runtimeClasspath include "sonar-*-plugin*.jar" } @@ -27,20 +27,28 @@ task copyRunnable(type: Copy) { from "bin/codeclimate-sonar" } +tasks.named("jar") { + dependsOn copyRunnable +} + +tasks.named("compileTestJava") { + dependsOn copyRunnable +} + task infra(dependsOn: ["copyPlugins", "copyLibs", "copyTestLibs", "copyRunnable", "jar"]) build.dependsOn(infra) test.dependsOn(infra) dependencies { - compile("com.github.codeclimate:codeclimate-ss-analyzer-wrapper:master-SNAPSHOT") + implementation("com.github.codeclimate:codeclimate-ss-analyzer-wrapper:beta-SNAPSHOT") // Plugins - compile("org.sonarsource.java:sonar-java-plugin:4.14.0.11784") + implementation("org.sonarsource.java:sonar-java-plugin:6.15.1.26025") - testCompile("org.assertj:assertj-core:2.8.0") - testCompile("org.skyscreamer:jsonassert:1.5.0") - testCompile("junit:junit:4.12") + testImplementation("org.assertj:assertj-core:2.8.0") + testImplementation("org.skyscreamer:jsonassert:1.5.0") + testImplementation("junit:junit:4.12") } test { diff --git a/fixtures/java_lib/main/java/Library.java b/fixtures/java_lib/main/java/Library.java index 4f12b30..8767fa6 100644 --- a/fixtures/java_lib/main/java/Library.java +++ b/fixtures/java_lib/main/java/Library.java @@ -2,7 +2,14 @@ * This Java source file was generated by the Gradle 'init' task. */ // FIXME + +import java.util.List; +import java.util.Set; + public class Library { + List myList; // Noncompliant + Set mySet; // Noncompliant + public static void main(String[] args) { } @@ -13,4 +20,20 @@ public void foo() { } } } + + public boolean bar(Number n) { + if (String.class.isInstance(n)) { // Noncompliant + return true; + } + String multi = """ + this is a single line string"""; + String textBlock = """ + \"\"\" this \nis + text block! + !!!! + """; + Pattern p = Pattern.compile(".*|a"); + Matcher m = p.matcher(multi); + return m.matches(); + } } diff --git a/fixtures/java_source_version/config_11.json b/fixtures/java_source_version/config_11.json new file mode 100644 index 0000000..f009314 --- /dev/null +++ b/fixtures/java_source_version/config_11.json @@ -0,0 +1,6 @@ +{ + "enabled": true, + "config": { + "sonar.java.source": "11" + } +} diff --git a/fixtures/java_source_version/config_15.json b/fixtures/java_source_version/config_15.json new file mode 100644 index 0000000..ce6fd56 --- /dev/null +++ b/fixtures/java_source_version/config_15.json @@ -0,0 +1,7 @@ +{ + "enabled": true, + "config": { + "sonar.java.source": "15", + "minimum_severity": "minor" + } +} diff --git a/src/test/java/integration/JavaSourceVersion.java b/src/test/java/integration/JavaSourceVersion.java index 769ed6f..ec9831d 100644 --- a/src/test/java/integration/JavaSourceVersion.java +++ b/src/test/java/integration/JavaSourceVersion.java @@ -11,4 +11,16 @@ public void specify_java_source_version_through_config() throws Exception { Shell.Process process = Shell.execute("build/codeclimate-sonar fixtures/java_source_version fixtures/java_source_version/config.json"); assertThat(process.stderr).contains("Configured Java source version (sonar.java.source): 6"); } + + @Test + public void specify_java_11_source_version_through_config() throws Exception { + Shell.Process process = Shell.execute("build/codeclimate-sonar fixtures/java_source_version fixtures/java_source_version/config_11.json"); + assertThat(process.stderr).contains("Configured Java source version (sonar.java.source): 11"); + } + + @Test + public void specify_java_15_source_version_through_config() throws Exception { + Shell.Process process = Shell.execute("build/codeclimate-sonar fixtures/java_source_version fixtures/java_source_version/config_15.json"); + assertThat(process.stderr).contains("Configured Java source version (sonar.java.source): 15"); + } } diff --git a/src/test/java/integration/SanityCheckTest.java b/src/test/java/integration/SanityCheckTest.java index b942b7f..443439b 100644 --- a/src/test/java/integration/SanityCheckTest.java +++ b/src/test/java/integration/SanityCheckTest.java @@ -14,8 +14,9 @@ public class SanityCheckTest { public void executeJavaLibFixture() throws Exception { String expectedOutput = File.read("src/test/resources/sanity_check_expected_issues.json"); - Shell.Process process = Shell.execute("build/codeclimate-sonar fixtures/java_lib"); + Shell.Process process = Shell.execute("build/codeclimate-sonar fixtures/java_lib fixtures/java_source_version/config_15.json"); + assertThat(process.stderr).contains("Configured Java source version (sonar.java.source): 15"); assertThat(process.exitCode).isEqualTo(0); assertThat(process.stdout) .withFailMessage("Issues must be split by a NULL (\\0) character") diff --git a/src/test/java/support/File.java b/src/test/java/support/File.java index e4f6b41..46bd3cc 100644 --- a/src/test/java/support/File.java +++ b/src/test/java/support/File.java @@ -5,7 +5,7 @@ import java.nio.file.Paths; public class File { - public static String read(String fileNmae) throws IOException { - return new String(Files.readAllBytes(Paths.get(fileNmae))); + public static String read(String fileName) throws IOException { + return new String(Files.readAllBytes(Paths.get(fileName))); } } diff --git a/src/test/resources/sanity_check_expected_issues.json b/src/test/resources/sanity_check_expected_issues.json index 5ed936e..ffc582f 100644 --- a/src/test/resources/sanity_check_expected_issues.json +++ b/src/test/resources/sanity_check_expected_issues.json @@ -1,26 +1,64 @@ [ { "type": "issue", - "check_name": "squid:S106", + "check_name": "java:S3740", "severity": "major", - "description": "Replace this use of System.out or System.err by a logger.", + "description": "Provide the parametrized type for this generic.", "content": { - "body": "

When logging a message there are several important requirements which must be fulfilled:

\n\n

If a program directly writes to the standard outputs, there is absolutely no way to comply with those requirements. That's why defining and using a\ndedicated logger is highly recommended.

\n

Noncompliant Code Example

\n
\nSystem.out.println(\"My Message\");  // Noncompliant\n
\n

Compliant Solution

\n
\nlogger.log(\"My Message\");\n
\n

See

\n" + "body": "

Generic types shouldn't be used raw (without type parameters) in variable declarations or return values. Doing so bypasses generic type checking,\nand defers the catch of unsafe code to runtime.

\n

Noncompliant Code Example

\n
\nList myList; // Noncompliant\nSet mySet; // Noncompliant\n
\n

Compliant Solution

\n
\nList<String> myList;\nSet<? extends Number> mySet;\n
" }, "location": { "path": "main/java/Library.java", "lines": { - "begin": 12, - "end": 12 + "begin": 10, + "end": 10 } }, "categories": [ - "Bug Risk" + "Clarity" + ] + }, + { + "type": "issue", + "check_name": "java:S3740", + "severity": "major", + "description": "Provide the parametrized type for this generic.", + "content": { + "body": "

Generic types shouldn't be used raw (without type parameters) in variable declarations or return values. Doing so bypasses generic type checking,\nand defers the catch of unsafe code to runtime.

\n

Noncompliant Code Example

\n
\nList myList; // Noncompliant\nSet mySet; // Noncompliant\n
\n

Compliant Solution

\n
\nList<String> myList;\nSet<? extends Number> mySet;\n
" + }, + "location": { + "path": "main/java/Library.java", + "lines": { + "begin": 11, + "end": 11 + } + }, + "categories": [ + "Clarity" + ] + }, + { + "type": "issue", + "check_name": "java:S1220", + "severity": "minor", + "description": "Move this file to a named package.", + "content": { + "body": "

According to the Java Language Specification:

\n
\n

Unnamed packages are provided by the Java platform principally for convenience when developing small or temporary applications or when just\n beginning development.

\n
\n

To enforce this best practice, classes located in default package can no longer be accessed from named ones since Java 1.4.

\n

Noncompliant Code Example

\n
\npublic class MyClass { /* ... */ }\n
\n

Compliant Solution

\n
\npackage org.example;\n\npublic class MyClass{ /* ... */ }\n
" + }, + "location": { + "path": "main/java/Library.java", + "lines": { + "begin": 1, + "end": 1 + } + }, + "categories": [ + "Style" ] }, { "type": "issue", - "check_name": "squid:S1134", + "check_name": "java:S1134", "severity": "major", "description": "Take the required action to fix the issue indicated by this comment.", "content": { @@ -39,7 +77,7 @@ }, { "type": "issue", - "check_name": "squid:S1186", + "check_name": "java:S1186", "severity": "critical", "description": "Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation.", "content": { @@ -48,12 +86,126 @@ "location": { "path": "main/java/Library.java", "lines": { - "begin": 6, - "end": 6 + "begin": 13, + "end": 13 + } + }, + "categories": [ + "Bug Risk" + ] + }, + { + "type": "issue", + "check_name": "java:S106", + "severity": "major", + "description": "Replace this use of System.out or System.err by a logger.", + "content": { + "body": "

When logging a message there are several important requirements which must be fulfilled:

\n\n

If a program directly writes to the standard outputs, there is absolutely no way to comply with those requirements. That's why defining and using a\ndedicated logger is highly recommended.

\n

Noncompliant Code Example

\n
\nSystem.out.println(\"My Message\");  // Noncompliant\n
\n

Compliant Solution

\n
\nlogger.log(\"My Message\");\n
\n

See

\n" + }, + "location": { + "path": "main/java/Library.java", + "lines": { + "begin": 19, + "end": 19 } }, "categories": [ "Bug Risk" ] + }, + { + "type": "issue", + "check_name": "java:S1854", + "severity": "major", + "description": "Remove this useless assignment to local variable \"textBlock\".", + "content": { + "body": "\u003cp\u003eA dead store happens when a local variable is assigned a value that is not read by any subsequent instruction. Calculating or retrieving a value\nonly to then overwrite it or throw it away, could indicate a serious error in the code. Even if it\u0027s not an error, it is at best a waste of resources.\nTherefore all calculated values should be used.\u003c/p\u003e\n\u003ch2\u003eNoncompliant Code Example\u003c/h2\u003e\n\u003cpre\u003e\ni \u003d a + b; // Noncompliant; calculation result not used before value is overwritten\ni \u003d compute();\n\u003c/pre\u003e\n\u003ch2\u003eCompliant Solution\u003c/h2\u003e\n\u003cpre\u003e\ni \u003d a + b;\ni +\u003d compute();\n\u003c/pre\u003e\n\u003ch2\u003eExceptions\u003c/h2\u003e\n\u003cp\u003eThis rule ignores initializations to -1, 0, 1, \u003ccode\u003enull\u003c/code\u003e, \u003ccode\u003etrue\u003c/code\u003e, \u003ccode\u003efalse\u003c/code\u003e and \u003ccode\u003e\"\"\u003c/code\u003e.\u003c/p\u003e\n\u003ch2\u003eSee\u003c/h2\u003e\n\u003cul\u003e\n \u003cli\u003e \u003ca href\u003d\"http://cwe.mitre.org/data/definitions/563.html\"\u003eMITRE, CWE-563\u003c/a\u003e - Assignment to Variable without Use (\u0027Unused Variable\u0027) \u003c/li\u003e\n \u003cli\u003e \u003ca href\u003d\"https://wiki.sei.cmu.edu/confluence/x/39UxBQ\"\u003eCERT, MSC13-C.\u003c/a\u003e - Detect and remove unused values \u003c/li\u003e\n \u003cli\u003e \u003ca href\u003d\"https://wiki.sei.cmu.edu/confluence/x/9DZGBQ\"\u003eCERT, MSC56-J.\u003c/a\u003e - Detect and remove superfluous code and values \u003c/li\u003e\n\u003c/ul\u003e" + }, + "location": { + "path": "main/java/Library.java", + "lines": { + "begin": 30, + "end": 34 + } + }, + "categories": [ + "Clarity" + ] + }, + { + "type": "issue", + "check_name": "java:S6202", + "severity": "major", + "description": "Replace this usage of \"String.class.isInstance()\" with \"instanceof String\".", + "content": { + "body": "

The instanceof construction is a preferred way to check whether a variable can be cast to some type statically because a compile-time\nerror will occur in case of incompatible types. The method isInstance() from java.lang.Class\nworks differently and does type check at runtime only, incompatible types will therefore not be detected early in the developement, potentially\nresulting in dead code. The isInstance() method should only be used in dynamic cases when the instanceof operator can't be\nused.

\n

This rule raises an issue when isInstance() is used and could be replaced with an instanceof check.

\n

Noncompliant Code Example

\n
\nint f(Object o) {\n  if (String.class.isInstance(o)) {  // Noncompliant\n    return 42;\n  }\n  return 0;\n}\n\nint f(Number n) {\n  if (String.class.isInstance(n)) {  // Noncompliant\n    return 42;\n  }\n  return 0;\n}\n\n
\n

Compliant Solution

\n
\nint f(Object o) {\n  if (o instanceof String) {  // Compliant\n    return 42;\n  }\n  return 0;\n}\n\nint f(Number n) {\n  if (n instanceof String) {  // Compile-time error\n    return 42;\n  }\n  return 0;\n}\n\nboolean fun(Object o, String c) throws ClassNotFoundException\n{\n  return Class.forName(c).isInstance(o); // Compliant, can't use instanceof operator here\n}\n
" + }, + "location": { + "path": "main/java/Library.java", + "lines": { + "begin": 25, + "end": 25 + } + }, + "categories": [ + "Clarity" + ] + }, + { + "type": "issue", + "check_name": "java:S5663", + "severity": "minor", + "description": "Use simple literal for a single-line string.", + "content": { + "body": "

If a string fits on a single line, without concatenation and escaped newlines, you should probably continue to use a string literal.

\n

Noncompliant Code Example

\n
\nString question = \"\"\"\n              What's the point, really?\"\"\";\n
\n

Compliant Solution

\n
\nString question = \"What's the point, really?\";\n
\n

See

\n" + }, + "location": { + "path": "main/java/Library.java", + "lines": { + "begin": 28, + "end": 29 + } + }, + "categories": [ + "Clarity" + ] + }, + { + "type": "issue", + "check_name": "java:S5665", + "severity": "minor", + "description": "Use '\\\"\"\"' to escape \"\"\".", + "content": { + "body": "

The use of escape sequences is mostly unnecessary in text blocks.

\n

Noncompliant Code Example

\n

\\n can be replaced by simply introducing the newline, \\\"\\\"\\\" it is sufficient to escape only the first qoute.

\n
\nString textBlock = \"\"\"\n        \\\"\\\"\\\" this \\nis\n        text  block!\n        !!!!\n      \"\"\";\n
\n

Compliant Solution

\n
\nString textBlock = \"\"\"\n        \\\"\"\" this\n        is\n        text  block!\n        !!!!\n      \"\"\";\n
\n

See

\n" + }, + "location": { + "path": "main/java/Library.java", + "lines": { + "begin": 31, + "end": 31 + } + }, + "categories": [ + "Clarity" + ] + }, + { + "type": "issue", + "check_name": "java:S1481", + "severity": "minor", + "description": "Remove this unused \"textBlock\" local variable.", + "content": { + "body": "\u003cp\u003eIf a local variable is declared but not used, it is dead code and should be removed. Doing so will improve maintainability because developers will\nnot wonder what the variable is used for.\u003c/p\u003e\n\u003ch2\u003eNoncompliant Code Example\u003c/h2\u003e\n\u003cpre\u003e\npublic int numberOfMinutes(int hours) {\n int seconds \u003d 0; // seconds is never used\n return hours * 60;\n}\n\u003c/pre\u003e\n\u003ch2\u003eCompliant Solution\u003c/h2\u003e\n\u003cpre\u003e\npublic int numberOfMinutes(int hours) {\n return hours * 60;\n}\n\u003c/pre\u003e" + }, + "location": { + "path": "main/java/Library.java", + "lines": { + "begin": 30, + "end": 30 + } + }, + "categories": [ + "Clarity" + ] } ]