Skip to content

Commit 0400535

Browse files
#42: Fix error thrown by plugin when proto file has import loop
When proto file imports itself, or when there are multiple proto files importing each other in a circle - no exceptions should be thrown.
1 parent 7bf3523 commit 0400535

File tree

3 files changed

+79
-32
lines changed

3 files changed

+79
-32
lines changed

src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoRootNode.java

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
package io.protostuff.jetbrains.plugin.psi;
22

3+
import static io.protostuff.jetbrains.plugin.psi.ProtoRootNode.ResolveMode.RESOLVE_ALL;
4+
import static io.protostuff.jetbrains.plugin.psi.ProtoRootNode.ResolveMode.RESOLVE_FIRST;
5+
36
import com.intellij.lang.ASTNode;
7+
import com.intellij.psi.PsiElement;
48
import com.intellij.psi.PsiReference;
59
import java.util.ArrayDeque;
610
import java.util.ArrayList;
711
import java.util.Arrays;
812
import java.util.Collection;
13+
import java.util.Collections;
914
import java.util.Deque;
1015
import java.util.HashSet;
1116
import java.util.List;
1217
import java.util.Objects;
18+
import java.util.Optional;
1319
import java.util.Queue;
1420
import java.util.Set;
21+
import java.util.function.Function;
1522
import java.util.stream.Collectors;
1623
import org.antlr.jetbrains.adapter.psi.AntlrPsiNode;
1724
import org.jetbrains.annotations.NotNull;
@@ -46,13 +53,11 @@ public String getPackageName() {
4653
* Resolve data type using given scope lookup list.
4754
*/
4855
public DataType resolve(String typeName, Deque<String> scopeLookupList) {
49-
return resolve(typeName, scopeLookupList, true);
56+
Optional<DataType> result = resolveFirst(proto -> proto.resolveLocal(typeName, scopeLookupList));
57+
return result.orElse(null);
5058
}
5159

52-
/**
53-
* Resolve data type using given scope lookup list.
54-
*/
55-
public DataType resolve(String typeName, Deque<String> scopeLookupList, boolean resolveInImports) {
60+
private DataType resolveLocal(String typeName, Deque<String> scopeLookupList) {
5661
DataType result = null;
5762
// A leading '.' (for example, .foo.bar.Baz) means to start from the outermost scope
5863
if (typeName.startsWith(".")) {
@@ -67,25 +72,7 @@ public DataType resolve(String typeName, Deque<String> scopeLookupList, boolean
6772
}
6873
}
6974
}
70-
if (result != null) {
71-
return result;
72-
}
73-
if (!resolveInImports) {
74-
return null;
75-
}
76-
List<ImportNode> importNodes = getImports();
77-
for (ImportNode importNode : importNodes) {
78-
ProtoRootNode targetProto = importNode.getTargetProto();
79-
if (targetProto != null) {
80-
boolean isPublic = importNode.isPublic();
81-
result = targetProto.resolve(typeName, scopeLookupList, isPublic);
82-
if (result != null) {
83-
return result;
84-
}
85-
86-
}
87-
}
88-
return null;
75+
return result;
8976
}
9077

9178
/**
@@ -186,8 +173,41 @@ public Collection<ExtendNode> getExtenstions(MessageNode target) {
186173
* Returns all extensions visible inside of this proto file.
187174
*/
188175
public Collection<ExtendNode> getExtensions() {
189-
List<ExtendNode> result = new ArrayList<>();
190-
result.addAll(getLocalExtensions());
176+
return resolveAll(ProtoRootNode::getLocalExtensions);
177+
}
178+
179+
enum ResolveMode {
180+
RESOLVE_FIRST,
181+
RESOLVE_ALL
182+
}
183+
184+
@NotNull
185+
private <T extends PsiElement> Optional<T> resolveFirst(Function<ProtoRootNode, T> extractor) {
186+
Collection<T> elements = resolveElementsImpl(RESOLVE_FIRST, proto -> {
187+
T result = extractor.apply(proto);
188+
if (result == null) {
189+
return Collections.emptyList();
190+
}
191+
return Collections.singletonList(result);
192+
});
193+
if (elements.isEmpty()) {
194+
return Optional.empty();
195+
}
196+
return Optional.of(elements.iterator().next());
197+
}
198+
199+
@NotNull
200+
private <T extends PsiElement> Collection<T> resolveAll(Function<ProtoRootNode, Collection<T>> extractor) {
201+
return resolveElementsImpl(RESOLVE_ALL, extractor);
202+
}
203+
204+
@NotNull
205+
private <T extends PsiElement> Collection<T> resolveElementsImpl(ResolveMode mode, Function<ProtoRootNode, Collection<T>> extractor) {
206+
List<T> result = new ArrayList<>();
207+
result.addAll(extractor.apply(this));
208+
if (stopLookup(mode, result)) {
209+
return result;
210+
}
191211
Queue<ImportNode> queue = new ArrayDeque<>();
192212
queue.addAll(getImports());
193213
Set<ProtoRootNode> processedProtos = new HashSet<>();
@@ -201,13 +221,20 @@ public Collection<ExtendNode> getExtensions() {
201221
}
202222
processedProtos.add(targetProto);
203223
if (targetProto != null) {
204-
result.addAll(targetProto.getLocalExtensions());
224+
result.addAll(extractor.apply(targetProto));
205225
queue.addAll(targetProto.getPublicImports());
226+
if (stopLookup(mode, result)) {
227+
break;
228+
}
206229
}
207230
}
208231
return result;
209232
}
210233

234+
private <T extends PsiElement> boolean stopLookup(ResolveMode mode, List<T> result) {
235+
return mode == ResolveMode.RESOLVE_FIRST && !result.isEmpty();
236+
}
237+
211238
/**
212239
* Returns local extensions declared in this proto file.
213240
*/

src/test/java/io/protostuff/jetbrains/plugin/reference/ReferenceTest.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
55
import io.protostuff.jetbrains.plugin.psi.DataType;
66
import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot;
7-
import org.junit.Assert;
87

98
/**
109
* Tests for resolving type references.
@@ -32,10 +31,15 @@ public void testEnumReference() {
3231
}
3332

3433
public void testImportedMessageReference() {
35-
// package is not set as files are copied to relative source root directly
3634
checkReferenceToDataType(".reference.ImportedMessage", "reference/ImportedMessageReferenceTestData.proto", "reference/ImportedTestData.proto");
3735
}
3836

37+
public void testProtoFileImportsItself() {
38+
// referenced type does not exist, so we expect that it is not resolvable
39+
// the only thing that we check here - is that we do not get StackOverflowError
40+
checkReferenceToDataType(null, "reference/ProtoFileImportsItself.proto");
41+
}
42+
3943
private void checkReferenceToDataType(String typeReference, String... file) {
4044
myFixture.configureByFiles(file);
4145
ProtoPsiFileRoot proto = (ProtoPsiFileRoot) myFixture.getFile();
@@ -46,8 +50,11 @@ private void checkReferenceToDataType(String typeReference, String... file) {
4650
}
4751
assertNotNull(elementAtCaret);
4852
PsiElement target = elementAtCaret.getReference().resolve();
49-
Assert.assertTrue(target instanceof DataType);
50-
DataType dataType = (DataType) target;
51-
Assert.assertEquals(typeReference, dataType.getQualifiedName());
53+
if (typeReference != null) {
54+
assertNotNull(target);
55+
assertTrue(target instanceof DataType);
56+
DataType dataType = (DataType) target;
57+
assertEquals(typeReference, dataType.getQualifiedName());
58+
}
5259
}
5360
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
syntax = "proto3";
2+
3+
package reference;
4+
5+
import "reference/ProtoFileImportsItself.proto";
6+
7+
//message SourceMessage {
8+
//
9+
//}
10+
11+
message TestMessage {
12+
Source<caret>Message message = 1;
13+
}

0 commit comments

Comments
 (0)