Skip to content

Commit

Permalink
Merge pull request #2202 from MohamedSabthar/static-code
Browse files Browse the repository at this point in the history
Add static code analyzer rules
  • Loading branch information
TharmiganK authored Nov 11, 2024
2 parents ab6b147 + f34e33c commit 09cc30f
Show file tree
Hide file tree
Showing 32 changed files with 1,306 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ load-tests/**/Dependencies.toml
# Ballerina related ignores
Ballerina.lock
velocity.log*

compiler-plugin-tests/**/target
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- [Add header name mapping support in record fields](https://github.com/ballerina-platform/ballerina-library/issues/7018)
- [Introduce util functions to convert query and header record with the `http:Query` and the `http:Header` annotations](https://github.com/ballerina-platform/ballerina-library/issues/7019)
- [Migrate client and service data binding lang utils usage into data.jsondata module utils `toJson` and `parserAsType`] (https://github.com/ballerina-platform/ballerina-library/issues/6747)
- [Add static code rules](https://github.com/ballerina-platform/ballerina-library/issues/7283)

### Fixed

Expand Down
1 change: 1 addition & 0 deletions compiler-plugin-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ test {
systemProperty "ballerina.offline.flag", "true"
useTestNG()
finalizedBy jacocoTestReport
testLogging.showStandardStreams = true
}

jacocoTestReport {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. 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.
*/

package io.ballerina.stdlib.http.compiler.staticcodeanalyzer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
* Helper class to consume the process streams.
*/
class ProcessOutputGobbler implements Runnable {
private final InputStream inputStream;
private final StringBuilder output;
private int exitCode;

public ProcessOutputGobbler(InputStream inputStream) {
this.inputStream = inputStream;
this.output = new StringBuilder();
}

@Override
public void run() {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
} catch (IOException e) {
this.output.append(e.getMessage());
}
}

public String getOutput() {
return output.toString();
}

public int getExitCode() {
return exitCode;
}

public void setExitCode(int exitCode) {
this.exitCode = exitCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. 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.
*/

package io.ballerina.stdlib.http.compiler.staticcodeanalyzer;

import org.testng.Assert;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;
import org.testng.internal.ExitCode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* This class includes tests for Ballerina Http static code analyzer.
*/
class StaticCodeAnalyzerTest {

private static final Path RESOURCE_PACKAGES_DIRECTORY = Paths
.get("src", "test", "resources", "static_code_analyzer", "ballerina_packages").toAbsolutePath();
private static final Path EXPECTED_JSON_OUTPUT_DIRECTORY = Paths.
get("src", "test", "resources", "static_code_analyzer", "expected_output").toAbsolutePath();
private static final Path BALLERINA_PATH = getBalCommandPath();
private static final Path JSON_RULES_FILE_PATH = Paths
.get("../", "compiler-plugin", "src", "main", "resources", "rules.json").toAbsolutePath();
private static final String SCAN_COMMAND = "scan";

private static Path getBalCommandPath() {
String balCommand = isWindows() ? "bal.bat" : "bal";
return Paths.get("../", "target", "ballerina-runtime", "bin", balCommand).toAbsolutePath();
}

@BeforeSuite
public void pullScanTool() throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), "tool", "pull", SCAN_COMMAND);
ProcessOutputGobbler output = getOutput(processBuilder.start());
if (Pattern.compile("tool 'scan:.+\\..+\\..+' successfully set as the active version\\.")
.matcher(output.getOutput()).find() || Pattern.compile("tool 'scan:.+\\..+\\..+' is already active\\.")
.matcher(output.getOutput()).find()) {
return;
}
Assert.assertFalse(ExitCode.hasFailure(output.getExitCode()));
}

@Test
public void validateRulesJson() throws IOException {
String expectedRules = "[" + Arrays.stream(HttpRule.values())
.map(HttpRule::toString).collect(Collectors.joining(",")) + "]";
String actualRules = Files.readString(JSON_RULES_FILE_PATH);
assertJsonEqual(normalizeJson(actualRules), normalizeJson(expectedRules));
}

@Test
public void testStaticCodeRules() throws IOException, InterruptedException {
for (HttpRule rule : HttpRule.values()) {
String targetPackageName = "rule" + rule.getId();
String actualJsonReport = StaticCodeAnalyzerTest.executeScanProcess(targetPackageName);
String expectedJsonReport = Files
.readString(EXPECTED_JSON_OUTPUT_DIRECTORY.resolve(targetPackageName + ".json"));
assertJsonEqual(actualJsonReport, expectedJsonReport);
}
}

public static String executeScanProcess(String targetPackage) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), SCAN_COMMAND);
processBuilder.directory(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage).toFile());
ProcessOutputGobbler output = getOutput(processBuilder.start());
Assert.assertFalse(ExitCode.hasFailure(output.getExitCode()));
return Files.readString(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage)
.resolve("target").resolve("report").resolve("scan_results.json"));
}

private static ProcessOutputGobbler getOutput(Process process) throws InterruptedException {
ProcessOutputGobbler outputGobbler = new ProcessOutputGobbler(process.getInputStream());
ProcessOutputGobbler errorGobbler = new ProcessOutputGobbler(process.getErrorStream());
Thread outputThread = new Thread(outputGobbler);
Thread errorThread = new Thread(errorGobbler);
outputThread.start();
errorThread.start();
int exitCode = process.waitFor();
outputGobbler.setExitCode(exitCode);
errorGobbler.setExitCode(exitCode);
outputThread.join();
errorThread.join();
return outputGobbler;
}

private void assertJsonEqual(String actual, String expected) {
Assert.assertEquals(normalizeJson(actual), normalizeJson(expected));
}

private static String normalizeJson(String json) {
String normalizedJson = json.replaceAll("\\s*\"\\s*", "\"")
.replaceAll("\\s*:\\s*", ":")
.replaceAll("\\s*,\\s*", ",")
.replaceAll("\\s*\\{\\s*", "{")
.replaceAll("\\s*}\\s*", "}")
.replaceAll("\\s*\\[\\s*", "[")
.replaceAll("\\s*]\\s*", "]")
.replaceAll("\n", "")
.replaceAll(":\".*module-ballerina-http", ":\"module-ballerina-http");
return isWindows() ? normalizedJson.replaceAll("/", "\\\\\\\\") : normalizedJson;
}

private static boolean isWindows() {
return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
org = "ballerina"
name = "rule1"
version = "0.1.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. 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.

import ballerina/http;

service on new http:Listener(8080) {
resource function default .() returns string? {
return;
}

resource function default greet() returns string? {
return;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. 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.

import ballerina/http;

service class GreetingService {
*http:Service;

resource function default .() returns string {
return "";
}

resource function default greet() returns string {
return "";
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. 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.

import ballerina/http;

type ServiceContract service object {
*http:Service;
resource function default .() returns string;
resource function default greet() returns string;
};

type MyContract service object {
*http:ServiceContract;
resource function default .() returns string;
resource function default greet() returns string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
org = "ballerina"
name = "rule2"
version = "0.1.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. 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.

import ballerina/http;

@http:ServiceConfig {
cors: {
allowOrigins: ["*"]
}
}
service on new http:Listener(8080) {

@http:ResourceConfig {
cors: {
allowOrigins: ["*"]
}
}
resource function get greet() returns string? {
return;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. 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.

import ballerina/http;

service class GreetingService {
*http:Service;

@http:ResourceConfig {
cors: {
allowOrigins: ["*"]
}
}
resource function get .() returns string? {
return;
}
};
Loading

0 comments on commit 09cc30f

Please sign in to comment.