Skip to content

Commit f85b157

Browse files
committed
fix: fix in Go annotator for single-line require and replace
1 parent 2d463ba commit f85b157

File tree

2 files changed

+255
-5
lines changed

2 files changed

+255
-5
lines changed

src/main/java/org/jboss/tools/intellij/componentanalysis/golang/GoCAAnnotator.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,12 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
5555
for (int i = 0; i < lines.length; i++) {
5656
String line = lines[i].trim();
5757

58-
// Track require/replace blocks
59-
if (line.startsWith("require")) {
60-
inRequireBlock = line.contains("(");
58+
if (line.startsWith("require") && isBlockStatement(line)) {
59+
inRequireBlock = true;
6160
continue;
6261
}
63-
if (line.startsWith("replace")) {
64-
inReplaceBlock = line.contains("(");
62+
if (line.startsWith("replace") && isBlockStatement(line)) {
63+
inReplaceBlock = true;
6564
continue;
6665
}
6766
if (line.equals(")")) {
@@ -192,4 +191,20 @@ private static Dependency createDependency(String modulePath, String version) {
192191

193192
return new Dependency("golang", namespace, name, version);
194193
}
194+
195+
/**
196+
* Determines if a line represents a block statement (require/replace with opening parenthesis).
197+
* Only considers "(" that appears at the end of the statement, ignoring any comments.
198+
*
199+
* Examples:
200+
* - "require (" -> true
201+
* - "require ( // comment" -> true
202+
* - "require golang.org/x/net v1.0 // comment with (" -> false
203+
* - "replace (" -> true
204+
*/
205+
private static boolean isBlockStatement(String line) {
206+
int commentIndex = line.indexOf("//");
207+
String statementPart = commentIndex >= 0 ? line.substring(0, commentIndex).trim() : line;
208+
return statementPart.endsWith("(");
209+
}
195210
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
12+
package org.jboss.tools.intellij.componentanalysis.golang;
13+
14+
import com.intellij.psi.PsiElement;
15+
import com.intellij.psi.PsiFile;
16+
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
17+
import org.jboss.tools.intellij.componentanalysis.Dependency;
18+
import org.junit.Test;
19+
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
/**
24+
* Comprehensive test to verify the GoCAAnnotator fix for all dependency declaration formats:
25+
* 1. Single-line require and replace statements
26+
* 2. Require and replace blocks
27+
* 3. Mixed cases
28+
*/
29+
public class GoCAAnnotatorTest extends BasePlatformTestCase {
30+
31+
/**
32+
* Testable subclass that exposes the protected getDependencies method
33+
*/
34+
private static class TestableGoCAAnnotator extends GoCAAnnotator {
35+
@Override
36+
public Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
37+
return super.getDependencies(file);
38+
}
39+
}
40+
41+
/**
42+
* Test Case 1: Single-line require and replace statements
43+
*/
44+
@Test
45+
public void testSingleLineRequireAndReplaceStatements() {
46+
String goModContent = """
47+
module test-single-line
48+
49+
go 1.20
50+
51+
require golang.org/x/net v0.10.0
52+
require golang.org/x/text v0.9.0 // indirect
53+
require google.golang.org/protobuf v1.30.0
54+
require github.com/google/go-cmp v0.5.9 // indirect
55+
56+
replace github.com/gin-gonic/gin v1.9.1 => github.com/myfork/gin v1.9.1-patch.2
57+
replace github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20250416174521-4eb003743b54
58+
replace golang.org/x/crypto => golang.org/x/crypto v0.1.0
59+
""";
60+
61+
TestableGoCAAnnotator annotator = new TestableGoCAAnnotator();
62+
PsiFile file = myFixture.configureByText("go.mod", goModContent);
63+
Map<Dependency, List<PsiElement>> dependencies = annotator.getDependencies(file);
64+
65+
assertEquals("Should find 7 dependencies (4 require + 3 replace targets)", 7, dependencies.size());
66+
67+
// Verify require dependencies
68+
assertTrue("Should contain golang.org/x/net", containsDependency(dependencies, "golang.org/x", "net", "v0.10.0"));
69+
assertTrue("Should contain golang.org/x/text", containsDependency(dependencies, "golang.org/x", "text", "v0.9.0"));
70+
assertTrue("Should contain google.golang.org/protobuf", containsDependency(dependencies, "google.golang.org", "protobuf", "v1.30.0"));
71+
assertTrue("Should contain github.com/google/go-cmp", containsDependency(dependencies, "github.com/google", "go-cmp", "v0.5.9"));
72+
73+
// Verify replace target dependencies
74+
assertTrue("Should contain github.com/myfork/gin", containsDependency(dependencies, "github.com/myfork", "gin", "v1.9.1-patch.2"));
75+
assertTrue("Should contain github.com/openshift/onsi-ginkgo/v2", containsDependency(dependencies, "github.com/openshift/onsi-ginkgo", "v2", "v2.6.1-0.20250416174521-4eb003743b54"));
76+
assertTrue("Should contain golang.org/x/crypto", containsDependency(dependencies, "golang.org/x", "crypto", "v0.1.0"));
77+
}
78+
79+
/**
80+
* Test Case 2: Require and replace blocks
81+
*/
82+
@Test
83+
public void testRequireAndReplaceBlocks() {
84+
String goModContent = """
85+
module test-blocks
86+
87+
go 1.20
88+
89+
require (
90+
github.com/gin-gonic/gin v1.4.0
91+
github.com/spf13/viper v1.3.2
92+
google.golang.org/protobuf v1.30.0
93+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect
94+
github.com/dgrijalva/jwt-go v3.2.0+incompatible
95+
)
96+
97+
replace (
98+
github.com/gin-gonic/gin v1.9.1 => github.com/myfork/gin v1.9.1-patch.2
99+
github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20250416174521-4eb003743b54
100+
golang.org/x/crypto => golang.org/x/crypto v0.1.0
101+
)
102+
""";
103+
104+
TestableGoCAAnnotator annotator = new TestableGoCAAnnotator();
105+
PsiFile file = myFixture.configureByText("go.mod", goModContent);
106+
Map<Dependency, List<PsiElement>> dependencies = annotator.getDependencies(file);
107+
108+
assertEquals("Should find 8 dependencies (5 require + 3 replace targets)", 8, dependencies.size());
109+
110+
// Verify require block dependencies
111+
assertTrue("Should contain github.com/gin-gonic/gin", containsDependency(dependencies, "github.com/gin-gonic", "gin", "v1.4.0"));
112+
assertTrue("Should contain github.com/spf13/viper", containsDependency(dependencies, "github.com/spf13", "viper", "v1.3.2"));
113+
assertTrue("Should contain google.golang.org/protobuf", containsDependency(dependencies, "google.golang.org", "protobuf", "v1.30.0"));
114+
assertTrue("Should contain golang.org/x/crypto", containsDependency(dependencies, "golang.org/x", "crypto", "v0.0.0-20190308221718-c2843e01d9a2"));
115+
assertTrue("Should contain github.com/dgrijalva/jwt-go", containsDependency(dependencies, "github.com/dgrijalva", "jwt-go", "v3.2.0+incompatible"));
116+
117+
// Verify replace block target dependencies
118+
assertTrue("Should contain github.com/myfork/gin", containsDependency(dependencies, "github.com/myfork", "gin", "v1.9.1-patch.2"));
119+
assertTrue("Should contain github.com/openshift/onsi-ginkgo/v2", containsDependency(dependencies, "github.com/openshift/onsi-ginkgo", "v2", "v2.6.1-0.20250416174521-4eb003743b54"));
120+
assertTrue("Should contain golang.org/x/crypto v0.1.0", containsDependency(dependencies, "golang.org/x", "crypto", "v0.1.0"));
121+
}
122+
123+
/**
124+
* Test Case 3: Mixed cases (single-line and blocks combined)
125+
*/
126+
@Test
127+
public void testMixedRequireAndReplaceStatements() {
128+
String goModContent = """
129+
module test-mixed
130+
131+
go 1.20
132+
133+
require golang.org/x/net v0.10.0
134+
135+
require (
136+
github.com/gin-gonic/gin v1.4.0
137+
github.com/spf13/viper v1.3.2
138+
)
139+
140+
require google.golang.org/protobuf v1.30.0
141+
142+
replace github.com/gin-gonic/gin v1.4.0 => github.com/myfork/gin v1.4.1
143+
144+
replace (
145+
golang.org/x/crypto => golang.org/x/crypto v0.1.0
146+
github.com/old/module => github.com/new/module v1.0.0
147+
)
148+
149+
require golang.org/x/text v0.9.0 // indirect
150+
""";
151+
152+
TestableGoCAAnnotator annotator = new TestableGoCAAnnotator();
153+
PsiFile file = myFixture.configureByText("go.mod", goModContent);
154+
Map<Dependency, List<PsiElement>> dependencies = annotator.getDependencies(file);
155+
156+
assertEquals("Should find 8 dependencies from mixed statements", 8, dependencies.size());
157+
158+
// Verify single-line require dependencies
159+
assertTrue("Should contain golang.org/x/net", containsDependency(dependencies, "golang.org/x", "net", "v0.10.0"));
160+
assertTrue("Should contain google.golang.org/protobuf", containsDependency(dependencies, "google.golang.org", "protobuf", "v1.30.0"));
161+
assertTrue("Should contain golang.org/x/text", containsDependency(dependencies, "golang.org/x", "text", "v0.9.0"));
162+
163+
// Verify require block dependencies
164+
assertTrue("Should contain github.com/gin-gonic/gin", containsDependency(dependencies, "github.com/gin-gonic", "gin", "v1.4.0"));
165+
assertTrue("Should contain github.com/spf13/viper", containsDependency(dependencies, "github.com/spf13", "viper", "v1.3.2"));
166+
167+
// Verify single-line replace target dependencies
168+
assertTrue("Should contain github.com/myfork/gin", containsDependency(dependencies, "github.com/myfork", "gin", "v1.4.1"));
169+
170+
// Verify replace block target dependencies
171+
assertTrue("Should contain golang.org/x/crypto", containsDependency(dependencies, "golang.org/x", "crypto", "v0.1.0"));
172+
assertTrue("Should contain github.com/new/module", containsDependency(dependencies, "github.com/new", "module", "v1.0.0"));
173+
}
174+
175+
/**
176+
* Test Case 4: Robust parentheses detection - ensure comments with '(' don't break parsing
177+
*/
178+
@Test
179+
public void testRobustParenthesesDetection() {
180+
String goModContent = """
181+
module test-robust-parentheses
182+
183+
go 1.20
184+
185+
require golang.org/x/net v0.10.0 // This comment has parentheses (test)
186+
require golang.org/x/text v0.9.0 // Another comment (with parens)
187+
188+
require (
189+
github.com/gin-gonic/gin v1.4.0 // Comment with (parentheses) inside block
190+
github.com/spf13/viper v1.3.2
191+
)
192+
193+
replace golang.org/x/crypto v0.1.0 => golang.org/x/crypto v0.2.0 // Comment (test)
194+
195+
replace (
196+
github.com/old/lib => github.com/new/lib v1.0.0
197+
)
198+
""";
199+
200+
TestableGoCAAnnotator annotator = new TestableGoCAAnnotator();
201+
PsiFile file = myFixture.configureByText("go.mod", goModContent);
202+
Map<Dependency, List<PsiElement>> dependencies = annotator.getDependencies(file);
203+
204+
assertEquals("Should find 6 dependencies despite comments with parentheses", 6, dependencies.size());
205+
206+
// Verify single-line requires with comments containing '(' are parsed correctly
207+
assertTrue("Should contain golang.org/x/net despite comment with ()",
208+
containsDependency(dependencies, "golang.org/x", "net", "v0.10.0"));
209+
assertTrue("Should contain golang.org/x/text despite comment with ()",
210+
containsDependency(dependencies, "golang.org/x", "text", "v0.9.0"));
211+
212+
// Verify block requires work correctly
213+
assertTrue("Should contain github.com/gin-gonic/gin from block",
214+
containsDependency(dependencies, "github.com/gin-gonic", "gin", "v1.4.0"));
215+
assertTrue("Should contain github.com/spf13/viper from block",
216+
containsDependency(dependencies, "github.com/spf13", "viper", "v1.3.2"));
217+
218+
// Verify replace statements work correctly
219+
assertTrue("Should contain golang.org/x/crypto from single-line replace",
220+
containsDependency(dependencies, "golang.org/x", "crypto", "v0.2.0"));
221+
assertTrue("Should contain github.com/new/lib from replace block",
222+
containsDependency(dependencies, "github.com/new", "lib", "v1.0.0"));
223+
}
224+
225+
/**
226+
* Helper method to check if dependencies contain a specific dependency with namespace, name, and version.
227+
*/
228+
private boolean containsDependency(Map<Dependency, List<PsiElement>> dependencies, String expectedNamespace, String expectedName, String expectedVersion) {
229+
return dependencies.keySet().stream()
230+
.anyMatch(dep -> "golang".equals(dep.getType()) &&
231+
expectedNamespace.equals(dep.getNamespace()) &&
232+
expectedName.equals(dep.getName()) &&
233+
expectedVersion.equals(dep.getVersion()));
234+
}
235+
}

0 commit comments

Comments
 (0)