Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
009e907
build: bump sonarlint-analysis-engine 10.24 -> 11.3 + LTA 2026.1 anal…
aksOps May 24, 2026
a3a42cd
feat(daemon): add SimpleActiveRule adapter for engine 11.x ActiveRule…
aksOps May 24, 2026
0761645
feat(hostplugin): NoOpAnalysisWarnings bean for engine 11.x sensors
aksOps May 24, 2026
991bc3e
feat(hostplugin): SonarPredictHostPlugin registers NoOpAnalysisWarnings
aksOps May 24, 2026
bb74c0c
fix(daemon): adapt PluginRuntime to engine 11.x SonarLanguage API
aksOps May 24, 2026
3fc9b77
fix(daemon): switch AnalysisService to public ActiveRule SPI
aksOps May 24, 2026
b9e0d4e
fix(daemon): IssueMapper handles RuleKey return type from engine 11.x
aksOps May 24, 2026
8a77967
build: emit sonar-predictor-host plugin JAR as classifier=host
aksOps May 24, 2026
e87e8ed
test(hostplugin): pin host JAR manifest contract
aksOps May 24, 2026
813f2fe
build(dist): ship sonar-predictor-host JAR in the offline plugin bundle
aksOps May 24, 2026
9cfd20a
docs(pom): document LTA 2026.1 pinning + upgrade procedure
aksOps May 24, 2026
04a93b0
test(hostplugin): verify host JAR loads and AnalysisWarnings bean wires
aksOps May 24, 2026
3c1296e
fix(hostplugin): correct lifespan + loader semantics so the host bean…
aksOps May 24, 2026
1bc8972
build: require Java 21 (sonarlint-analysis-engine 11.x ships major-65…
aksOps May 24, 2026
f42d844
build(test): drop ch.qos.logback dep — Socket flagged it obfuscatedFi…
aksOps May 24, 2026
d600cdf
fix(daemon): EngineLog.current uses AtomicReference, not volatile fie…
aksOps May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
java-version: '21'
cache: maven

- name: Set up Node.js (JS/TS analyzer tests need a Node runtime)
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ jobs:
with:
fetch-depth: 0

- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
java-version: '21'

- name: Set up Node.js 20
uses: actions/setup-node@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ jobs:
with:
fetch-depth: 0

- name: Set up JDK 17 + GPG
- name: Set up JDK 21 + GPG
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
java-version: '21'
server-id: central
server-username: OSS_NEXUS_USER
server-password: OSS_NEXUS_PASS
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ jobs:
with:
fetch-depth: 0

# JDK 17 is the project's build/runtime target. Temurin is the safe default.
- name: Set up JDK 17
# JDK 21 is the project's build/runtime target (required by
# sonarlint-analysis-engine 11.x / LTA 2026.1). Temurin is the safe default.
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
java-version: '21'

# The JS/TS analyzer plugin spawns Node at runtime to lint JS/TS sources,
# so Node must be on PATH when the scan runs (not just at build time).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ The JSON output carries both fields on every issue.

| Requirement | For |
|---|---|
| **Java 17+** (JDK or JRE) | running the CLI and daemon — auto-discovered (`JAVA_HOME` → `PATH` → common install locations) |
| **Java 21+** (JDK or JRE) | running the CLI and daemon — auto-discovered (`JAVA_HOME` → `PATH` → common install locations) |
| **Linux or macOS** | the daemon uses Unix domain sockets (Windows support is on the roadmap) |
| **`git`** | the `check --diff` workflow |
| **Node.js 18.17+** | JavaScript / TypeScript analysis |
Expand Down
116 changes: 102 additions & 14 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,32 +54,51 @@
</issueManagement>

<properties>
<maven.compiler.release>17</maven.compiler.release>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!--
Java 21 minimum: sonarlint-analysis-engine 11.3.0.85510 (LTA 2026.1)
ships class files at major version 65 (Java 21). Compiling against
it requires a JDK 21+ toolchain. CI's setup-java step is pinned to
21 in .github/workflows/*.yml to match.
-->
<maven.compiler.release>21</maven.compiler.release>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<jackson.version>2.17.2</jackson.version>
<junit.version>5.10.2</junit.version>
<sonarlint.engine.version>10.24.0.81415</sonarlint.engine.version>
<!--
These versions track SonarQube Server 2026.1 LTA (released 2025-12-11).

To upgrade to a future LTA:
1. Look up the new LTA's bundled analyzer versions in the release
notes at https://docs.sonarsource.com/sonarqube-server/&lt;v&gt;/setup-and-upgrade/release-upgrade-notes/
2. Bump all 11 version properties below.
3. Run `mvn clean test` — the daemon test suite is the canary.
4. If a NEW required host bean appears (NoSuchBeanDefinitionException
on a type other than AnalysisWarnings), add an @SonarLintSide no-op
class in src/main/java/.../hostplugin/ and one context.addExtension(...)
line in SonarPredictHostPlugin.define(). See spec §7.2 for details.
-->
<sonarlint.engine.version>11.3.0.85510</sonarlint.engine.version>
<picocli.version>4.7.7</picocli.version>

<!--
The 10 SonarSource analyzer plugins — the runtime assets the daemon
loads. Single source of truth for the maven-dependency-plugin copy
executions below, which fetch them through Maven (so settings.xml
mirrors / Nexus / proxy apply). Versions match engine 10.24.0.81415.
mirrors / Nexus / proxy apply). Versions match engine 11.3.0.85510 (LTA 2026.1).
-->
<sonar.plugin.java.version>8.15.0.39343</sonar.plugin.java.version>
<sonar.plugin.python.version>5.5.0.23291</sonar.plugin.python.version>
<sonar.plugin.javascript.version>10.24.0.33043</sonar.plugin.javascript.version>
<sonar.plugin.php.version>3.46.0.13151</sonar.plugin.php.version>
<sonar.plugin.kotlin.version>3.2.0.7239</sonar.plugin.kotlin.version>
<sonar.plugin.java.version>8.29.0.43460</sonar.plugin.java.version>
<sonar.plugin.python.version>5.22.0.33216</sonar.plugin.python.version>
<sonar.plugin.javascript.version>12.5.0.41048</sonar.plugin.javascript.version>
<sonar.plugin.php.version>3.57.0.15976</sonar.plugin.php.version>
<sonar.plugin.kotlin.version>3.6.0.9326</sonar.plugin.kotlin.version>
<sonar.plugin.go.version>1.18.1.827</sonar.plugin.go.version>
<sonar.plugin.ruby.version>1.19.0.471</sonar.plugin.ruby.version>
<sonar.plugin.scala.version>1.19.0.484</sonar.plugin.scala.version>
<sonar.plugin.html.version>3.19.0.5695</sonar.plugin.html.version>
<sonar.plugin.xml.version>2.13.0.5938</sonar.plugin.xml.version>
<sonar.plugin.ruby.version>1.22.0.1992</sonar.plugin.ruby.version>
<sonar.plugin.scala.version>1.23.0.2394</sonar.plugin.scala.version>
<sonar.plugin.html.version>3.27.0.7699</sonar.plugin.html.version>
<sonar.plugin.xml.version>2.17.0.7895</sonar.plugin.xml.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -129,6 +148,18 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.27.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.15.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -178,6 +209,27 @@
<phase>process-classes</phase>
<goals><goal>jar</goal></goals>
</execution>
<execution>
<id>host-plugin-jar</id>
<phase>process-test-classes</phase>
<goals><goal>jar</goal></goals>
<configuration>
<classifier>host</classifier>
<includes>
<include>io/github/randomcodespace/sonarpredict/hostplugin/**</include>
</includes>
<archive>
<manifestEntries>
<Plugin-Class>io.github.randomcodespace.sonarpredict.hostplugin.SonarPredictHostPlugin</Plugin-Class>
<Plugin-Key>sonarpredict-host</Plugin-Key>
<Plugin-Name>Sonar Predictor Host</Plugin-Name>
<Plugin-Version>${project.version}</Plugin-Version>
<Sonar-Version>9.9</Sonar-Version>
<SonarLint-Supported>true</SonarLint-Supported>
</manifestEntries>
</archive>
</configuration>
</execution>
</executions>
</plugin>

Expand Down Expand Up @@ -348,6 +400,42 @@
</executions>
</plugin>

<!--
Copy the host plugin jar into the dev plugins/ directory so
that AnalysisService (no-arg constructor → plugins/) and
PluginRuntime.loadAll pick it up during mvn test.

The host-plugin-jar jar-plugin execution (process-test-classes)
produces target/sonar-predictor-${project.version}-host.jar.
This antrun execution binds to the SAME phase so it runs
immediately after the jar is written.

The file is gitignored (plugins/*.jar). In production the host
jar ships inside the dist zip's plugins/ directory via the
assembly descriptor, so no separate production step is needed.
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-host-plugin-for-tests</id>
<phase>process-test-classes</phase>
<goals><goal>run</goal></goals>
<configuration>
<target>
<copy todir="${project.basedir}/plugins" overwrite="true">
<fileset dir="${project.build.directory}">
<include name="${project.artifactId}-${project.version}-host.jar"/>
</fileset>
</copy>
</target>
</configuration>
</execution>
</executions>
</plugin>

<!-- Assemble the dist bundle: bin/ launchers + lib/ fat jars +
plugins/ analyzer jars, zipped. Output:
target/sonar-predictor-dist-${project.version}.zip. -->
Expand Down
11 changes: 11 additions & 0 deletions src/main/assembly/dist.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@
</includes>
</fileSet>

<!-- The host plugin JAR produced by the host-plugin-jar maven-jar-plugin
execution. Ships alongside the 10 analyzer JARs in plugins/ so the
daemon's PluginRuntime.loadAll picks it up via the *.jar glob. -->
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>plugins</outputDirectory>
<includes>
<include>sonar-predictor-*-host.jar</include>
</includes>
</fileSet>

<!-- The two shaded fat jars produced by maven-shade-plugin's
shade-cli + shade-daemon executions. Their on-disk names are
sonar-predictor-{cli,daemon}-<version>.jar; the launcher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.stream.Stream;

import org.sonarsource.sonarlint.core.analysis.AnalysisScheduler;
import org.sonarsource.sonarlint.core.analysis.api.ActiveRule;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;
import org.sonarsource.sonarlint.core.analysis.api.AnalysisResults;
import org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;
Expand Down Expand Up @@ -510,12 +510,8 @@ static List<ActiveRule> resolveActiveRules(
}
String languageKey = language.getSonarLanguageKey();
for (String ruleKey : ruleKeys) {
ActiveRule activeRule = new ActiveRule(ruleKey, languageKey);
Map<String, String> params = paramDefaults.paramsFor(ruleKey);
if (!params.isEmpty()) {
activeRule.setParams(params);
}
rules.add(activeRule);
rules.add(SimpleActiveRule.of(ruleKey, languageKey, params));
}
}
return rules;
Expand Down Expand Up @@ -622,14 +618,10 @@ private List<ActiveRule> profileRulesFrom(String profileRef) {
}
// The analyzer-registered parameter defaults are the baseline; the
// profile's own <parameters> override them where it sets a value.
ActiveRule active = new ActiveRule(rule.ruleKey(), languageKey);
Map<String, String> params =
new java.util.HashMap<>(ruleParameterDefaults.paramsFor(rule.ruleKey()));
params.putAll(rule.parameters());
if (!params.isEmpty()) {
active.setParams(Map.copyOf(params));
}
rules.add(active);
rules.add(SimpleActiveRule.of(rule.ruleKey(), languageKey, params));
}
return rules;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;

import org.sonarsource.sonarlint.core.commons.log.LogOutput;
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
Expand All @@ -21,6 +22,8 @@
*/
public final class EngineLog implements LogOutput {

private static final AtomicReference<EngineLog> CURRENT = new AtomicReference<>();

private final List<String> messages = new CopyOnWriteArrayList<>();

/**
Expand All @@ -47,9 +50,21 @@ public static void install() {
public static EngineLog installAndCapture() {
EngineLog target = new EngineLog();
SonarLintLogger.get().setTarget(target);
CURRENT.set(target);
return target;
}

/**
* Returns the most recently {@link #install installed} {@code EngineLog},
* or {@code null} if none has been installed yet. Tests use this to assert
* on engine messages emitted by code paths (such as
* {@code PluginRuntime.loadAll}) that install their own {@code EngineLog}
* internally without exposing the reference.
*/
public static EngineLog current() {
return CURRENT.get();
}

@Override
public void log(String formattedMessage, Level level) {
if (formattedMessage != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private IssueMapper() {
public static io.github.randomcodespace.sonarpredict.protocol.dto.Issue toDto(
Issue engineIssue, Path baseDir, RuleCatalog catalog) {
return map(
engineIssue.getRuleKey(),
engineIssue.getRuleKey().toString(),
resolveFilePath(engineIssue.getInputFile(), baseDir),
engineIssue.getTextRange(),
engineIssue.getMessage(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ public static LoadedPlugins loadFrom(Set<Path> jars) {
V1_LANGUAGES,
false,
detectNodeVersion());
// Second argument is disabledPluginsForAnalysis — keys in this set are excluded
// from getAnalysisPluginInstancesByKeys() and therefore never installed into any
// Spring analysis container. Pass an empty set so the host plugin's extensions
// (NoOpAnalysisWarnings) are visible to sensors such as HtmlSensor that autowire
// AnalysisWarnings. The engine's own "additionalAllowedPlugins" list (textdeveloper,
// textenterprise, etc.) is built internally by PluginsLoader and is separate.
PluginsLoadResult result = new PluginsLoader().load(config, Set.of());
return result.getLoadedPlugins();
}
Expand All @@ -126,7 +132,7 @@ public static LoadedPlugins loadFrom(Set<Path> jars) {
public static Set<SonarLanguage> loadedLanguagesFor(Set<String> loadedPluginKeys) {
Set<SonarLanguage> loaded = EnumSet.noneOf(SonarLanguage.class);
for (SonarLanguage language : V1_LANGUAGES) {
if (loadedPluginKeys.contains(language.getPluginKey())) {
if (loadedPluginKeys.contains(language.getPlugin().getKey())) {
loaded.add(language);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.github.randomcodespace.sonarpredict.daemon;

import java.util.Map;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.rule.RuleKey;

/**
* Adapter for engine 11.x: {@code AnalysisConfiguration.Builder.addActiveRules} now takes
* the public {@link ActiveRule} interface; 10.x's internal {@code ActiveRule} class is gone
* and no public concrete ships. This record is the minimal implementation that satisfies
* the interface for our analyses (no severity / template / qProfile metadata needed offline).
*/
public record SimpleActiveRule(
RuleKey ruleKey,
String language,
String severity,
String internalKey,
String templateRuleKey,
Map<String, String> params,
String qpKey) implements ActiveRule {

@Override
public String param(String key) {
return params.get(key);
}

public static SimpleActiveRule of(String ruleKey, String language, Map<String, String> params) {
return new SimpleActiveRule(
RuleKey.parse(ruleKey),
language,
null,
null,
null,
params == null ? Map.of() : Map.copyOf(params),
null);
}
}
Loading
Loading