diff --git a/ changelog.md b/ changelog.md
index 961e48c6..ec6ff1c5 100644
--- a/ changelog.md
+++ b/ changelog.md
@@ -5,6 +5,7 @@ This file contains all the notable changes done to the Ballerina TCP package thr
### Added
- [Support logging raw template value in log APIs](https://github.com/ballerina-platform/ballerina-library/issues/3331)
+- [Add static code rules](https://github.com/ballerina-platform/ballerina-library/issues/7283)
## [2.5.1] - 2023-01-04
diff --git a/.gitignore b/.gitignore
index 2e53f73c..a927bbee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,5 @@ integration-tests/Dependencies.toml
# Ballerina
velocity.log*
*Ballerina.lock
+
+compiler-plugin-tests/**/target
diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml
index 9a0522db..b1add967 100644
--- a/ballerina/Ballerina.toml
+++ b/ballerina/Ballerina.toml
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "log"
-version = "2.11.0"
+version = "2.11.1"
authors = ["Ballerina"]
keywords = ["level", "format"]
repository = "https://github.com/ballerina-platform/module-ballerina-log"
@@ -15,12 +15,18 @@ graalvmCompatible = true
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "log-native"
-version = "2.11.0"
-path = "../native/build/libs/log-native-2.11.0.jar"
+version = "2.11.1"
+path = "../native/build/libs/log-native-2.11.1-SNAPSHOT.jar"
+
+[[platform.java21.dependency]]
+groupId = "io.ballerina.stdlib"
+artifactId = "log-compiler-plugin"
+version = "2.11.1"
+path = "../compiler-plugin/build/libs/log-compiler-plugin-2.11.1-SNAPSHOT.jar"
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "log-test-utils"
-version = "2.11.0"
-path = "../test-utils/build/libs/log-test-utils-2.11.0.jar"
+version = "2.11.1"
+path = "../test-utils/build/libs/log-test-utils-2.11.1-SNAPSHOT.jar"
scope = "testOnly"
diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml
index 9dd639d8..9b2ce5eb 100644
--- a/ballerina/CompilerPlugin.toml
+++ b/ballerina/CompilerPlugin.toml
@@ -1,6 +1,6 @@
[plugin]
id = "log-compiler-plugin"
-class = "io.ballerina.stdlib.log.plugin.LogCompilerPlugin"
+class = "io.ballerina.stdlib.log.compiler.LogCompilerPlugin"
[[dependency]]
-path = "../compiler-plugin/build/libs/log-compiler-plugin-2.11.0.jar"
+path = "../compiler-plugin/build/libs/log-compiler-plugin-2.11.1-SNAPSHOT.jar"
diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml
index 4213f4db..3279dc0d 100644
--- a/ballerina/Dependencies.toml
+++ b/ballerina/Dependencies.toml
@@ -76,7 +76,7 @@ modules = [
[[package]]
org = "ballerina"
name = "log"
-version = "2.11.0"
+version = "2.11.1"
dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml
index 7681323d..d6906b0f 100644
--- a/build-config/resources/Ballerina.toml
+++ b/build-config/resources/Ballerina.toml
@@ -18,6 +18,12 @@ artifactId = "log-native"
version = "@toml.version@"
path = "../native/build/libs/log-native-@project.version@.jar"
+[[platform.java21.dependency]]
+groupId = "io.ballerina.stdlib"
+artifactId = "log-compiler-plugin"
+version = "@toml.version@"
+path = "../compiler-plugin/build/libs/log-compiler-plugin-@project.version@.jar"
+
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "log-test-utils"
diff --git a/build-config/resources/CompilerPlugin.toml b/build-config/resources/CompilerPlugin.toml
index 2db40f06..472dbb75 100644
--- a/build-config/resources/CompilerPlugin.toml
+++ b/build-config/resources/CompilerPlugin.toml
@@ -1,6 +1,6 @@
[plugin]
id = "log-compiler-plugin"
-class = "io.ballerina.stdlib.log.plugin.LogCompilerPlugin"
+class = "io.ballerina.stdlib.log.compiler.LogCompilerPlugin"
[[dependency]]
path = "../compiler-plugin/build/libs/log-compiler-plugin-@project.version@.jar"
diff --git a/codecov.yml b/codecov.yml
index 0e744bb3..ec993452 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,9 +1,11 @@
fixes:
- "ballerina/log/*/::log-ballerina/"
+ - "io/ballerina/stdlib/log/compiler/::./compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/"
ignore:
- "**/tests"
- "test-utils"
+ - "compiler-plugin"
coverage:
precision: 2
diff --git a/compiler-plugin-tests/build.gradle b/compiler-plugin-tests/build.gradle
new file mode 100644
index 00000000..51c24ee6
--- /dev/null
+++ b/compiler-plugin-tests/build.gradle
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org).
+ *
+ * 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.
+ */
+
+plugins {
+ id 'java'
+ id 'checkstyle'
+ id 'com.github.spotbugs'
+ id 'jacoco'
+}
+
+jacoco {
+ toolVersion = "${jacocoVersion}"
+ reportsDirectory = file("$project.buildDir/reports/jacoco")
+}
+
+description = 'Ballerina - Log Compiler Plugin Tests'
+
+dependencies {
+ checkstyle project(':checkstyle')
+ checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}"
+
+ implementation project(':log-compiler-plugin')
+
+ testImplementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}"
+ testImplementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}"
+ testImplementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}"
+
+ implementation group: 'org.testng', name: 'testng', version: "${testngVersion}"
+}
+
+tasks.withType(JavaCompile) {
+ options.encoding = 'UTF-8'
+}
+
+sourceCompatibility = JavaVersion.VERSION_21
+
+test {
+ useTestNG() {
+ suites 'src/test/resources/testng.xml'
+ }
+ testLogging.showStandardStreams = true
+ testLogging {
+ events "PASSED", "FAILED", "SKIPPED"
+ afterSuite { desc, result ->
+ if (!desc.parent) { // will match the outermost suite
+ def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
+ def startItem = '| ', endItem = ' |'
+ def repeatLength = startItem.length() + output.length() + endItem.length()
+ println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength))
+ }
+ }
+ }
+ finalizedBy jacocoTestReport
+}
+
+jacocoTestReport {
+ dependsOn test
+ reports {
+ xml.required = true
+ }
+ sourceSets project(':log-compiler-plugin').sourceSets.main
+}
+
+spotbugsTest {
+ def classLoader = plugins["com.github.spotbugs"].class.classLoader
+ def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence")
+ def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort")
+ ignoreFailures = true
+ effort = SpotBugsEffort.MAX
+ reportLevel = SpotBugsConfidence.LOW
+ reportsDir = file("$project.buildDir/reports/spotbugs")
+ def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml")
+ if (excludeFile.exists()) {
+ it.excludeFilter = excludeFile
+ }
+ reports {
+ text.enabled = true
+ }
+}
+
+spotbugsMain {
+ enabled false
+}
+
+task validateSpotbugs() {
+ doLast {
+ if (spotbugsMain.reports.size() > 0 &&
+ spotbugsMain.reports[0].destination.exists() &&
+ spotbugsMain.reports[0].destination.text.readLines().size() > 0) {
+ spotbugsMain.reports[0].destination?.eachLine {
+ println 'Failure: ' + it
+ }
+ throw new GradleException("Spotbugs rule violations were found.");
+ }
+ }
+}
+
+tasks.withType(Checkstyle) {
+ exclude '**/module-info.java'
+}
+
+checkstyle {
+ toolVersion "${project.checkstylePluginVersion}"
+ configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml")
+ configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")]
+}
+
+checkstyleMain {
+ enabled false
+}
+
+spotbugsTest.finalizedBy validateSpotbugs
+checkstyleTest.dependsOn ':checkstyle:downloadCheckstyleRuleFiles'
+
+compileJava {
+ doFirst {
+ options.compilerArgs = [
+ '--module-path', classpath.asPath,
+ ]
+ classpath = files()
+ }
+}
+
+test.dependsOn ":log-ballerina:build"
+build.dependsOn ":log-ballerina:build"
diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/ProcessOutputGobbler.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/ProcessOutputGobbler.java
new file mode 100644
index 00000000..9dc26dae
--- /dev/null
+++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/ProcessOutputGobbler.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org).
+ *
+ * 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.log.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.
+ * @since 2.12.0
+ */
+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;
+ }
+}
diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java
new file mode 100644
index 00000000..c5f155d2
--- /dev/null
+++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org).
+ *
+ * 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.log.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.io.PrintStream;
+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.
+ * @since 2.12.0
+ */
+class StaticCodeAnalyzerTest {
+
+ PrintStream outStream = System.out;
+
+ 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(LogRule.values())
+ .map(LogRule::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 (LogRule rule : LogRule.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);
+ }
+ }
+
+ private 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-log", ":\"module-ballerina-log");
+ return isWindows() ? normalizedJson.replaceAll("/", "\\\\\\\\") : normalizedJson;
+ }
+
+ private static boolean isWindows() {
+ return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows");
+ }
+}
diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml
new file mode 100644
index 00000000..efbb839e
--- /dev/null
+++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml
@@ -0,0 +1,8 @@
+[package]
+org = "ballerina"
+name = "rule1"
+version = "0.1.0"
+distribution = "2201.10.3"
+
+[build-options]
+observabilityIncluded = true
diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Config.toml b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Config.toml
new file mode 100644
index 00000000..fc9c4cd5
--- /dev/null
+++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Config.toml
@@ -0,0 +1,2 @@
+password = "xyz"
+user = "abc"
diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal
new file mode 100644
index 00000000..4a126265
--- /dev/null
+++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal
@@ -0,0 +1,35 @@
+// 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/log;
+import ballerina/log as lg;
+
+public function main() {
+ log:printInfo(password);
+ log:printError(string `Error: ${password}`);
+ log:printWarn(`Error: ${password}`);
+ log:printError("Error " + password);
+ log:printWarn("Warning", password = password);
+ log:printError("Error", password = password, user = user);
+ lg:printError(password, user = user);
+}
+
+function log() {
+ log:printInfo("Info");
+}
+
+configurable string password = ?;
+configurable string user = ?;
diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json
new file mode 100644
index 00000000..29138eb9
--- /dev/null
+++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json
@@ -0,0 +1,182 @@
+[
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 19,
+ "endLine": 19,
+ "startColumn": 18,
+ "endColumn": 26,
+ "startOffset": 711,
+ "length": 8
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ },
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 20,
+ "endLine": 20,
+ "startColumn": 36,
+ "endColumn": 44,
+ "startOffset": 758,
+ "length": 8
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ },
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 21,
+ "endLine": 21,
+ "startColumn": 28,
+ "endColumn": 36,
+ "startOffset": 799,
+ "length": 8
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ },
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 22,
+ "endLine": 22,
+ "startColumn": 30,
+ "endColumn": 38,
+ "startOffset": 842,
+ "length": 8
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ },
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 23,
+ "endLine": 23,
+ "startColumn": 40,
+ "endColumn": 48,
+ "startOffset": 893,
+ "length": 8
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ },
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 24,
+ "endLine": 24,
+ "startColumn": 39,
+ "endColumn": 47,
+ "startOffset": 943,
+ "length": 8
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ },
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 24,
+ "endLine": 24,
+ "startColumn": 56,
+ "endColumn": 60,
+ "startOffset": 960,
+ "length": 4
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ },
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 25,
+ "endLine": 25,
+ "startColumn": 19,
+ "endColumn": 27,
+ "startOffset": 986,
+ "length": 8
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ },
+ {
+ "location": {
+ "filePath": "main.bal",
+ "startLine": 25,
+ "endLine": 25,
+ "startColumn": 36,
+ "endColumn": 40,
+ "startOffset": 1003,
+ "length": 4
+ },
+ "rule": {
+ "id": "ballerina/log:1",
+ "numericId": 1,
+ "description": "Potentially-sensitive configurable variables are logged",
+ "ruleKind": "VULNERABILITY"
+ },
+ "source": "BUILT_IN",
+ "fileName": "rule1/main.bal",
+ "filePath": "/Users/danesh/ballerina-platform/module-ballerina-log/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+ }
+]
diff --git a/compiler-plugin-tests/src/test/resources/testng.xml b/compiler-plugin-tests/src/test/resources/testng.xml
new file mode 100644
index 00000000..85e46103
--- /dev/null
+++ b/compiler-plugin-tests/src/test/resources/testng.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle
index 23a09353..6cffee93 100644
--- a/compiler-plugin/build.gradle
+++ b/compiler-plugin/build.gradle
@@ -30,7 +30,7 @@ dependencies {
implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}"
implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}"
implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}"
-
+ implementation group: 'io.ballerina.scan', name: 'scan-command', version: "${balScanVersion}"
implementation project(":log-native")
}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/plugin/LogCodeModifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/LogCodeModifier.java
similarity index 99%
rename from compiler-plugin/src/main/java/io/ballerina/stdlib/log/plugin/LogCodeModifier.java
rename to compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/LogCodeModifier.java
index b875183b..8a3089cd 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/plugin/LogCodeModifier.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/LogCodeModifier.java
@@ -16,7 +16,7 @@
* under the License.
*/
-package io.ballerina.stdlib.log.plugin;
+package io.ballerina.stdlib.log.compiler;
import io.ballerina.compiler.syntax.tree.AbstractNodeFactory;
import io.ballerina.compiler.syntax.tree.FunctionArgumentNode;
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/plugin/LogCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/LogCompilerPlugin.java
similarity index 63%
rename from compiler-plugin/src/main/java/io/ballerina/stdlib/log/plugin/LogCompilerPlugin.java
rename to compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/LogCompilerPlugin.java
index b42bd27a..fc701eb7 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/plugin/LogCompilerPlugin.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/LogCompilerPlugin.java
@@ -16,18 +16,25 @@
* under the License.
*/
-package io.ballerina.stdlib.log.plugin;
+package io.ballerina.stdlib.log.compiler;
import io.ballerina.projects.plugins.CompilerPlugin;
import io.ballerina.projects.plugins.CompilerPluginContext;
+import io.ballerina.scan.ScannerContext;
+import io.ballerina.stdlib.log.compiler.staticcodeanalyzer.StaticCodeAnalyzer;
/**
* log module Compiler plugin.
*/
public class LogCompilerPlugin extends CompilerPlugin {
+ private static final String SCANNER_CONTEXT = "ScannerContext";
+
@Override
- public void init(CompilerPluginContext compilerPluginContext) {
-// compilerPluginContext.addCodeModifier(new LogCodeModifier());
+ public void init(CompilerPluginContext context) {
+ Object object = context.userData().get(SCANNER_CONTEXT);
+ if (object instanceof ScannerContext scannerContext) {
+ context.addCodeAnalyzer(new StaticCodeAnalyzer(scannerContext.getReporter()));
+ }
}
}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/LogRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/LogRule.java
new file mode 100644
index 00000000..54eb37c3
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/LogRule.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org).
+ *
+ * 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.log.compiler.staticcodeanalyzer;
+
+import io.ballerina.scan.Rule;
+
+import static io.ballerina.scan.RuleKind.VULNERABILITY;
+import static io.ballerina.stdlib.log.compiler.staticcodeanalyzer.RuleFactory.createRule;
+
+/**
+ * Enum to hold the log rules.
+ * @since 2.12.0
+ */
+public enum LogRule {
+ AVOID_LOGGING_CONFIGURABLE_VARIABLES(createRule(1,
+ "Potentially-sensitive configurable variables are logged", VULNERABILITY));
+ private final Rule rule;
+
+
+ LogRule(Rule rule) {
+ this.rule = rule;
+ }
+
+ public int getId() {
+ return this.rule.numericId();
+ }
+
+ public Rule getRule() {
+ return this.rule;
+ }
+
+ @Override
+ public String toString() {
+ return "{\"id\":" + this.getId() + ", \"kind\":\"" + this.rule.kind() + "\"," +
+ " \"description\" : \"" + this.rule.description() + "\"}";
+ }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/LogStatementAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/LogStatementAnalyzer.java
new file mode 100644
index 00000000..2cbc31e9
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/LogStatementAnalyzer.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org).
+ *
+ * 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.log.compiler.staticcodeanalyzer;
+
+import io.ballerina.compiler.api.SemanticModel;
+import io.ballerina.compiler.api.symbols.Symbol;
+import io.ballerina.compiler.api.symbols.VariableSymbol;
+import io.ballerina.compiler.syntax.tree.BinaryExpressionNode;
+import io.ballerina.compiler.syntax.tree.ChildNodeEntry;
+import io.ballerina.compiler.syntax.tree.ExpressionNode;
+import io.ballerina.compiler.syntax.tree.ExpressionStatementNode;
+import io.ballerina.compiler.syntax.tree.ImportOrgNameNode;
+import io.ballerina.compiler.syntax.tree.ImportPrefixNode;
+import io.ballerina.compiler.syntax.tree.InterpolationNode;
+import io.ballerina.compiler.syntax.tree.ModulePartNode;
+import io.ballerina.compiler.syntax.tree.NamedArgumentNode;
+import io.ballerina.compiler.syntax.tree.Node;
+import io.ballerina.compiler.syntax.tree.NodeList;
+import io.ballerina.compiler.syntax.tree.PositionalArgumentNode;
+import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode;
+import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode;
+import io.ballerina.compiler.syntax.tree.TemplateExpressionNode;
+import io.ballerina.projects.Document;
+import io.ballerina.projects.plugins.AnalysisTask;
+import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
+import io.ballerina.scan.Reporter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static io.ballerina.stdlib.log.compiler.staticcodeanalyzer.LogRule.AVOID_LOGGING_CONFIGURABLE_VARIABLES;
+
+/**
+ * This class analyzes the log statements and checks if the log statement is logging a configurable variable.
+ *
+ * @since 2.12.0
+ */
+public class LogStatementAnalyzer implements AnalysisTask {
+
+ public static final String CONFIGURABLE_QUALIFIER = "CONFIGURABLE";
+ public static final String LOG_MODULE = "log";
+ public static final String BALLERINA_ORG = "ballerina";
+
+ final List logFunctions = Arrays.asList("printInfo", "printError", "printWarn");
+
+ List semanticModels = new ArrayList<>();
+
+ private final Reporter reporter;
+
+ public LogStatementAnalyzer(Reporter reporter) {
+ this.reporter = reporter;
+ }
+ @Override
+ public void perform(SyntaxNodeAnalysisContext context) {
+ // If semantic model is empty, we get semantic models of all the modules in the package and save it in a list
+ if (semanticModels.isEmpty()) {
+ context.currentPackage().modules().forEach(module -> {
+ SemanticModel semanticModel = module.getCompilation().getSemanticModel();
+ semanticModels.add(semanticModel);
+ });
+ }
+
+ Document document = context.currentPackage().module(context.moduleId()).document(context.documentId());
+ List importPrefix = new ArrayList<>();
+ if (document.syntaxTree().rootNode() instanceof ModulePartNode modulePartNode) {
+ importPrefix = modulePartNode.imports().stream()
+ .filter(importDeclarationNode -> {
+ ImportOrgNameNode importOrgNameNode = importDeclarationNode.orgName().orElse(null);
+ return importOrgNameNode != null && BALLERINA_ORG.equals(importOrgNameNode.orgName().text());
+ })
+ .filter(importDeclarationNode -> importDeclarationNode.moduleName().stream().anyMatch(
+ moduleNameNode -> LOG_MODULE.equals(moduleNameNode.text())))
+ .map(importDeclarationNode -> {
+ ImportPrefixNode importPrefixNode = importDeclarationNode.prefix().orElse(null);
+ return importPrefixNode != null ? importPrefixNode.prefix().text() : LOG_MODULE;
+ }).toList();
+ }
+
+
+ if (context.node() instanceof ExpressionStatementNode expressionStatementNode) {
+ // Check if the log statement has a configurable qualifier
+ List childlist = expressionStatementNode.expression().childEntries().stream()
+ .toList();
+
+ if (childlist.size() < 4) {
+ return;
+ }
+
+ Node firstChild = childlist.getFirst().node().orElse(null);
+ if (firstChild instanceof QualifiedNameReferenceNode qualifiedNameReferenceNode
+ && importPrefix.contains(qualifiedNameReferenceNode.modulePrefix().text())
+ && logFunctions.contains(qualifiedNameReferenceNode.identifier().text())) {
+
+ // The argument of the log function is the third child. second and fourth child are the parentheses
+ NodeList logArgumentNodeList = childlist.get(2).nodeList();
+ // collect all the log function arguments.
+ List list = logArgumentNodeList.stream().filter(node -> node instanceof PositionalArgumentNode ||
+ node instanceof NamedArgumentNode)
+ .toList();
+
+ for (Node node : list) {
+ if (node instanceof PositionalArgumentNode positionalArgumentNode) {
+ positionalArgumentNode.childEntries().forEach(childNodeEntry -> {
+ Node expression = childNodeEntry.node().orElse(null);
+ if (expression instanceof SimpleNameReferenceNode simpleNameReferenceNode) {
+ checkConfigurableQualifier(simpleNameReferenceNode, document);
+ } else if (expression instanceof TemplateExpressionNode templateExpressionNode) {
+ templateExpressionNode.content().forEach(content -> {
+ if (content instanceof InterpolationNode interpolationNode) {
+ interpolationNode.childEntries().forEach(interpolationChild -> {
+ Node interpolationExpression = interpolationChild.node().orElse(null);
+ if (interpolationExpression instanceof SimpleNameReferenceNode
+ simpleNameReferenceNode) {
+ checkConfigurableQualifier(simpleNameReferenceNode, document);
+ }
+ });
+ }
+ });
+ } else if (expression instanceof BinaryExpressionNode binaryExpressionNode) {
+ binaryExpressionNode.childEntries().forEach(childEntry -> {
+ Node childNode = childEntry.node().orElse(null);
+ if (childNode instanceof SimpleNameReferenceNode simpleNameReferenceNode) {
+ checkConfigurableQualifier(simpleNameReferenceNode, document);
+ }
+ });
+ }
+ });
+ } else if (node instanceof NamedArgumentNode namedArgumentNode) {
+ checkConfigurableQualifier(namedArgumentNode.expression(), document);
+ }
+ }
+ }
+ }
+ }
+
+ private void checkConfigurableQualifier(ExpressionNode argumentNode, Document document) {
+ semanticModels.forEach(semanticModel -> {
+ Symbol symbol = semanticModel.symbol(argumentNode).orElse(null);
+ if (symbol instanceof VariableSymbol variableSymbol) {
+ variableSymbol.qualifiers().stream().filter(qualifier -> qualifier
+ .toString().equals(CONFIGURABLE_QUALIFIER)).forEach(qualifier -> {
+ this.reporter.reportIssue(document,
+ argumentNode.location(),
+ AVOID_LOGGING_CONFIGURABLE_VARIABLES.getId());
+ });
+ }
+ });
+ }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/RuleFactory.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/RuleFactory.java
new file mode 100644
index 00000000..7a68f2a8
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/RuleFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.log.compiler.staticcodeanalyzer;
+
+import io.ballerina.scan.Rule;
+import io.ballerina.scan.RuleKind;
+
+/**
+ * {@code RuleFactory} contains the logic to create a {@link Rule}.
+ *
+ * @since 2.12.0
+ */
+public class RuleFactory {
+ public static Rule createRule(int id, String description, RuleKind kind) {
+ return new RuleImpl(id, description, kind);
+ }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/RuleImpl.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/RuleImpl.java
new file mode 100644
index 00000000..384690c2
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/RuleImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org).
+ *
+ * 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.log.compiler.staticcodeanalyzer;
+
+import io.ballerina.scan.Rule;
+import io.ballerina.scan.RuleKind;
+
+/**
+ * {@code RuleFactory} contains the logic to create a {@link Rule}.
+ *
+ * @since 2.12.0
+ */
+class RuleImpl implements Rule {
+ private final int id;
+ private final String description;
+ private final RuleKind kind;
+
+ RuleImpl(int id, String description, RuleKind kind) {
+ this.id = id;
+ this.description = description;
+ this.kind = kind;
+ }
+
+ @Override
+ public String id() {
+ return Integer.toString(this.id);
+ }
+
+ @Override
+ public int numericId() {
+ return this.id;
+ }
+
+ @Override
+ public String description() {
+ return this.description;
+ }
+
+ @Override
+ public RuleKind kind() {
+ return this.kind;
+ }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/StaticCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/StaticCodeAnalyzer.java
new file mode 100644
index 00000000..2403eedb
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/log/compiler/staticcodeanalyzer/StaticCodeAnalyzer.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org).
+ *
+ * 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.log.compiler.staticcodeanalyzer;
+
+import io.ballerina.projects.plugins.CodeAnalysisContext;
+import io.ballerina.projects.plugins.CodeAnalyzer;
+import io.ballerina.scan.Reporter;
+
+import java.util.List;
+
+import static io.ballerina.compiler.syntax.tree.SyntaxKind.CALL_STATEMENT;
+
+/**
+ * Static code analyzer for log module.
+ *
+ * @since 2.12.0
+ */
+public class StaticCodeAnalyzer extends CodeAnalyzer {
+ private final Reporter reporter;
+
+ public StaticCodeAnalyzer(Reporter reporter) {
+ this.reporter = reporter;
+ }
+
+ @Override
+ public void init(CodeAnalysisContext analysisContext) {
+ analysisContext.addSyntaxNodeAnalysisTask(new LogStatementAnalyzer(reporter),
+ List.of(CALL_STATEMENT));
+ }
+}
diff --git a/compiler-plugin/src/main/java/module-info.java b/compiler-plugin/src/main/java/module-info.java
index bcfdf10e..74865172 100644
--- a/compiler-plugin/src/main/java/module-info.java
+++ b/compiler-plugin/src/main/java/module-info.java
@@ -20,4 +20,5 @@
requires io.ballerina.lang;
requires io.ballerina.parser;
requires io.ballerina.tools.api;
+ requires io.ballerina.scan;
}
diff --git a/compiler-plugin/src/main/resources/rules.json b/compiler-plugin/src/main/resources/rules.json
new file mode 100644
index 00000000..d335f9f1
--- /dev/null
+++ b/compiler-plugin/src/main/resources/rules.json
@@ -0,0 +1,7 @@
+[
+ {
+ "id": 1,
+ "kind": "VULNERABILITY",
+ "description": "Potentially-sensitive configurable variables are logged"
+ }
+]
diff --git a/gradle.properties b/gradle.properties
index 42be9e95..48525130 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,6 +9,8 @@ shadowJarPluginVersion=8.1.1
downloadPluginVersion=5.4.0
releasePluginVersion=2.6.0
ballerinaGradlePluginVersion=2.3.0
+balScanVersion=0.5.0
+jacocoVersion=0.8.10
#stdlib dependencies
stdlibIoVersion=1.7.0
diff --git a/settings.gradle b/settings.gradle
index eca70c49..3752449f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -39,6 +39,7 @@ include ':log-native'
include ':log-test-utils'
include ':log-ballerina'
include ':log-compiler-plugin'
+include ':log-compiler-plugin-tests'
include ':log-integration-tests'
project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle")
@@ -46,6 +47,7 @@ project(':log-native').projectDir = file('native')
project(':log-test-utils').projectDir = file('test-utils')
project(':log-ballerina').projectDir = file('ballerina')
project(':log-compiler-plugin').projectDir = file('compiler-plugin')
+project(':log-compiler-plugin-tests').projectDir = file('compiler-plugin-tests')
project(':log-integration-tests').projectDir = file('integration-tests')
gradleEnterprise {