Skip to content

Commit 03048dc

Browse files
authored
Add NoSystemScopeDependencies cleanup recipe (#7055)
* Add recipe to fix Sonar rule S3422: remove system scope from Maven dependencies This recipe removes <scope>system</scope> and <systemPath> from Maven dependencies when the artifact is available in configured repositories. System scope is deprecated, non-portable, and problematic because it requires a machine-specific systemPath and is not packaged in artifacts. The recipe only removes system scope if the dependency can be verified to exist in a configured Maven repository by checking available versions. Dependencies not found in any repository are left unchanged. Fixes Sonar finding: 'Dependencies should not have "system" scope' * Add NoSystemScopeDependencies to Maven best practices Include the new recipe in the BestPractices composite recipe and add its entry to recipes.csv. * Remove unused import and add null safety for metadata versioning * Revert unrelated package-lock.json changes * Restore package-lock.json to match latest main * Resolve property placeholders in version before checking metadata
1 parent 9b72f91 commit 03048dc

4 files changed

Lines changed: 341 additions & 0 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.maven.cleanup;
17+
18+
import lombok.Getter;
19+
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.maven.MavenDownloadingException;
23+
import org.openrewrite.maven.MavenIsoVisitor;
24+
import org.openrewrite.maven.tree.MavenMetadata;
25+
import org.openrewrite.xml.RemoveContentVisitor;
26+
import org.openrewrite.xml.tree.Xml;
27+
28+
public class NoSystemScopeDependencies extends Recipe {
29+
30+
@Getter
31+
final String displayName = "Dependencies should not have `system` scope";
32+
33+
@Getter
34+
final String description = "Replaces `<scope>system</scope>` with the default compile scope and removes " +
35+
"`<systemPath>` for dependencies that are available in configured repositories.";
36+
37+
@Override
38+
public TreeVisitor<?, ExecutionContext> getVisitor() {
39+
return new MavenIsoVisitor<ExecutionContext>() {
40+
@Override
41+
public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
42+
if ((isDependencyTag() || isManagedDependencyTag()) &&
43+
"system".equals(tag.getChildValue("scope").orElse(null))) {
44+
45+
String groupId = tag.getChildValue("groupId").orElse(null);
46+
String artifactId = tag.getChildValue("artifactId").orElse(null);
47+
String version = tag.getChildValue("version").orElse(null);
48+
if (groupId == null || artifactId == null || version == null) {
49+
return super.visitTag(tag, ctx);
50+
}
51+
52+
// Resolve property placeholders (e.g. ${some.version}) against the POM
53+
String resolvedVersion = getResolutionResult().getPom().getValue(version);
54+
if (resolvedVersion == null) {
55+
return super.visitTag(tag, ctx);
56+
}
57+
58+
try {
59+
MavenMetadata metadata = downloadMetadata(groupId, artifactId, ctx);
60+
if (metadata.getVersioning() == null ||
61+
!metadata.getVersioning().getVersions().contains(resolvedVersion)) {
62+
return super.visitTag(tag, ctx);
63+
}
64+
} catch (MavenDownloadingException e) {
65+
return super.visitTag(tag, ctx);
66+
}
67+
68+
tag.getChild("scope").ifPresent(
69+
scope -> doAfterVisit(new RemoveContentVisitor<>(scope, false, true)));
70+
tag.getChild("systemPath").ifPresent(
71+
systemPath -> doAfterVisit(new RemoveContentVisitor<>(systemPath, false, true)));
72+
maybeUpdateModel();
73+
}
74+
return super.visitTag(tag, ctx);
75+
}
76+
};
77+
}
78+
}

rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ recipeList:
2222
- org.openrewrite.maven.cleanup.ExplicitDependencyVersion
2323
- org.openrewrite.maven.cleanup.ExplicitPluginGroupId
2424
- org.openrewrite.maven.cleanup.ExplicitPluginVersion
25+
- org.openrewrite.maven.cleanup.NoSystemScopeDependencies
2526
- org.openrewrite.maven.cleanup.PrefixlessExpressions
2627
- org.openrewrite.maven.OrderPomElements
2728
- org.openrewrite.maven.RemoveDuplicateDependencies

rewrite-maven/src/main/resources/META-INF/rewrite/recipes.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpgradeToModelVersion4
7373
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ReplaceModulesWithSubprojects,Replace modules with subprojects,Maven 4 model version 4.1.0 deprecates the `<modules>` element in favor of `<subprojects>` to eliminate confusion with Java's Platform Module System (JPMS). This recipe renames `<modules>` to `<subprojects>` and `<module>` children to `<subproject>`.,3,,Maven,,
7474
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveMavenWrapper,Remove Maven wrapper,"Remove Maven wrapper files from a project. This includes the `mvnw` and `mvnw.cmd` scripts, and the `.mvn/wrapper` directory.",4,,Maven,,
7575
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.DependencyManagementDependencyRequiresVersion,Dependency management dependencies should have a version,"If they don't have a version, they can't possibly affect dependency resolution anywhere, and can be safely removed.",1,Cleanup,Maven,,
76+
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.NoSystemScopeDependencies,Dependencies should not have `system` scope,"Replaces `<scope>system</scope>` with the default compile scope and removes `<systemPath>` for dependencies that are available in configured repositories.",1,Cleanup,Maven,,
7677
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.ExplicitDependencyVersion,Add explicit dependency versions,"Add explicit dependency versions to POMs for reproducibility, as the `LATEST` and `RELEASE` version keywords are deprecated.",1,Cleanup,Maven,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
7778
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.ExplicitPluginGroupId,Add explicit `groupId` to Maven plugins,Add the default `<groupId>org.apache.maven.plugins</groupId>` to plugins for clarity.,1,Cleanup,Maven,,
7879
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.ExplicitPluginVersion,Add explicit plugin versions,"Add explicit plugin versions to POMs for reproducibility, as [MNG-4173](https://issues.apache.org/jira/browse/MNG-4173) removes automatic version resolution for POM plugins.",1,Cleanup,Maven,,
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.maven.cleanup;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.openrewrite.test.RewriteTest;
20+
21+
import static org.openrewrite.maven.Assertions.pomXml;
22+
23+
class NoSystemScopeDependenciesTest implements RewriteTest {
24+
25+
@Test
26+
void removesSystemScopeAndSystemPath() {
27+
rewriteRun(
28+
spec -> spec.recipe(new NoSystemScopeDependencies()),
29+
pomXml(
30+
"""
31+
<project>
32+
<groupId>test</groupId>
33+
<artifactId>test</artifactId>
34+
<version>1.0-SNAPSHOT</version>
35+
<dependencies>
36+
<dependency>
37+
<groupId>com.google.guava</groupId>
38+
<artifactId>guava</artifactId>
39+
<version>29.0-jre</version>
40+
<scope>system</scope>
41+
<systemPath>${project.basedir}/lib/guava-29.0-jre.jar</systemPath>
42+
</dependency>
43+
</dependencies>
44+
</project>
45+
""",
46+
"""
47+
<project>
48+
<groupId>test</groupId>
49+
<artifactId>test</artifactId>
50+
<version>1.0-SNAPSHOT</version>
51+
<dependencies>
52+
<dependency>
53+
<groupId>com.google.guava</groupId>
54+
<artifactId>guava</artifactId>
55+
<version>29.0-jre</version>
56+
</dependency>
57+
</dependencies>
58+
</project>
59+
"""
60+
)
61+
);
62+
}
63+
64+
@Test
65+
void removesSystemScopeWithoutSystemPath() {
66+
rewriteRun(
67+
spec -> spec.recipe(new NoSystemScopeDependencies()),
68+
pomXml(
69+
"""
70+
<project>
71+
<groupId>test</groupId>
72+
<artifactId>test</artifactId>
73+
<version>1.0-SNAPSHOT</version>
74+
<dependencies>
75+
<dependency>
76+
<groupId>com.google.guava</groupId>
77+
<artifactId>guava</artifactId>
78+
<version>29.0-jre</version>
79+
<scope>system</scope>
80+
</dependency>
81+
</dependencies>
82+
</project>
83+
""",
84+
"""
85+
<project>
86+
<groupId>test</groupId>
87+
<artifactId>test</artifactId>
88+
<version>1.0-SNAPSHOT</version>
89+
<dependencies>
90+
<dependency>
91+
<groupId>com.google.guava</groupId>
92+
<artifactId>guava</artifactId>
93+
<version>29.0-jre</version>
94+
</dependency>
95+
</dependencies>
96+
</project>
97+
"""
98+
)
99+
);
100+
}
101+
102+
@Test
103+
void removesSystemScopeInDependencyManagement() {
104+
rewriteRun(
105+
spec -> spec.recipe(new NoSystemScopeDependencies()),
106+
pomXml(
107+
"""
108+
<project>
109+
<groupId>test</groupId>
110+
<artifactId>test</artifactId>
111+
<version>1.0-SNAPSHOT</version>
112+
<dependencyManagement>
113+
<dependencies>
114+
<dependency>
115+
<groupId>com.google.guava</groupId>
116+
<artifactId>guava</artifactId>
117+
<version>29.0-jre</version>
118+
<scope>system</scope>
119+
<systemPath>${project.basedir}/lib/guava-29.0-jre.jar</systemPath>
120+
</dependency>
121+
</dependencies>
122+
</dependencyManagement>
123+
</project>
124+
""",
125+
"""
126+
<project>
127+
<groupId>test</groupId>
128+
<artifactId>test</artifactId>
129+
<version>1.0-SNAPSHOT</version>
130+
<dependencyManagement>
131+
<dependencies>
132+
<dependency>
133+
<groupId>com.google.guava</groupId>
134+
<artifactId>guava</artifactId>
135+
<version>29.0-jre</version>
136+
</dependency>
137+
</dependencies>
138+
</dependencyManagement>
139+
</project>
140+
"""
141+
)
142+
);
143+
}
144+
145+
@Test
146+
void removesSystemScopeWhenVersionIsProperty() {
147+
rewriteRun(
148+
spec -> spec.recipe(new NoSystemScopeDependencies()),
149+
pomXml(
150+
"""
151+
<project>
152+
<groupId>test</groupId>
153+
<artifactId>test</artifactId>
154+
<version>1.0-SNAPSHOT</version>
155+
<properties>
156+
<guava.version>29.0-jre</guava.version>
157+
</properties>
158+
<dependencies>
159+
<dependency>
160+
<groupId>com.google.guava</groupId>
161+
<artifactId>guava</artifactId>
162+
<version>${guava.version}</version>
163+
<scope>system</scope>
164+
<systemPath>${project.basedir}/lib/guava-29.0-jre.jar</systemPath>
165+
</dependency>
166+
</dependencies>
167+
</project>
168+
""",
169+
"""
170+
<project>
171+
<groupId>test</groupId>
172+
<artifactId>test</artifactId>
173+
<version>1.0-SNAPSHOT</version>
174+
<properties>
175+
<guava.version>29.0-jre</guava.version>
176+
</properties>
177+
<dependencies>
178+
<dependency>
179+
<groupId>com.google.guava</groupId>
180+
<artifactId>guava</artifactId>
181+
<version>${guava.version}</version>
182+
</dependency>
183+
</dependencies>
184+
</project>
185+
"""
186+
)
187+
);
188+
}
189+
190+
@Test
191+
void doesNotRemoveSystemScopeWhenNotInRepo() {
192+
rewriteRun(
193+
spec -> spec.recipe(new NoSystemScopeDependencies()),
194+
pomXml(
195+
"""
196+
<project>
197+
<groupId>test</groupId>
198+
<artifactId>test</artifactId>
199+
<version>1.0-SNAPSHOT</version>
200+
<dependencies>
201+
<dependency>
202+
<groupId>com.example.local</groupId>
203+
<artifactId>proprietary-lib</artifactId>
204+
<version>1.0</version>
205+
<scope>system</scope>
206+
<systemPath>${project.basedir}/lib/proprietary-lib-1.0.jar</systemPath>
207+
</dependency>
208+
</dependencies>
209+
</project>
210+
"""
211+
)
212+
);
213+
}
214+
215+
@Test
216+
void doesNotModifyNonSystemScope() {
217+
rewriteRun(
218+
spec -> spec.recipe(new NoSystemScopeDependencies()),
219+
pomXml(
220+
"""
221+
<project>
222+
<groupId>test</groupId>
223+
<artifactId>test</artifactId>
224+
<version>1.0-SNAPSHOT</version>
225+
<dependencies>
226+
<dependency>
227+
<groupId>com.google.guava</groupId>
228+
<artifactId>guava</artifactId>
229+
<version>29.0-jre</version>
230+
<scope>test</scope>
231+
</dependency>
232+
</dependencies>
233+
</project>
234+
"""
235+
)
236+
);
237+
}
238+
239+
@Test
240+
void doesNotModifyDependencyWithoutScope() {
241+
rewriteRun(
242+
spec -> spec.recipe(new NoSystemScopeDependencies()),
243+
pomXml(
244+
"""
245+
<project>
246+
<groupId>test</groupId>
247+
<artifactId>test</artifactId>
248+
<version>1.0-SNAPSHOT</version>
249+
<dependencies>
250+
<dependency>
251+
<groupId>com.google.guava</groupId>
252+
<artifactId>guava</artifactId>
253+
<version>29.0-jre</version>
254+
</dependency>
255+
</dependencies>
256+
</project>
257+
"""
258+
)
259+
);
260+
}
261+
}

0 commit comments

Comments
 (0)