Skip to content

Commit 0ee75f2

Browse files
#66: Resolve imports relative to file's location
Many projects are storing proto files in a locations that are not marked as source/resource root. As a result, imports are highlighted with error "file does not exist", and all imported types are not resolvable. Plugin can try to resolve imports relative to source file's location using following rules: 1. If `package` is not set, try to look in the same folder where source file is. 2. If package is set, try to look from parent folder. 3. Parent folder should be computed as source file's folders, with removed corresponding parts of the package. For example: `/home/user/project/foo/bar/hello.proto`: ```proto package foo.bar; import "baz/import.proto"; ``` Import should be resolved to `/home/user/project/baz/import.proto`.
1 parent c13643e commit 0ee75f2

12 files changed

+161
-11
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.intellij.openapi.module.ModuleManager;
66
import com.intellij.openapi.module.ModuleUtilCore;
77
import com.intellij.openapi.vfs.VirtualFile;
8+
import com.intellij.psi.PsiElement;
89
import com.intellij.psi.PsiFile;
910
import com.intellij.psi.PsiFileSystemItem;
1011
import com.intellij.psi.PsiManager;
@@ -79,7 +80,7 @@ public ProtoPsiFileRoot getTarget() {
7980
}
8081

8182
private ProtoPsiFileRoot getTarget(@NotNull String filename, @NotNull Module module) {
82-
Collection<PsiFileSystemItem> roots = new FilePathReferenceProvider().getRoots(module);
83+
Collection<PsiFileSystemItem> roots = new FilePathReferenceProvider().getRoots(module, getRoot());
8384
for (PsiFileSystemItem root : roots) {
8485
VirtualFile file = root.getVirtualFile().findFileByRelativePath(getFilename());
8586
if (file != null) {
@@ -101,6 +102,15 @@ private ProtoPsiFileRoot getTarget(@NotNull String filename, @NotNull Module mod
101102
return null;
102103
}
103104

105+
@Nullable
106+
private ProtoPsiFileRoot getRoot() {
107+
PsiElement node = this;
108+
while (!(node instanceof ProtoPsiFileRoot || node == null)) {
109+
node = node.getParent();
110+
}
111+
return (ProtoPsiFileRoot) node;
112+
}
113+
104114
@Nullable
105115
private String getFilename() {
106116
String text = getText();

src/main/java/io/protostuff/jetbrains/plugin/reference/file/AllSourceRootsProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.intellij.openapi.module.Module;
44
import com.intellij.openapi.roots.ModuleRootManager;
55
import com.intellij.openapi.vfs.VirtualFile;
6+
import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot;
67

78
/**
89
* Returns "all source roots". In the IntelliJ IDEA it include source & resource roots,
@@ -12,7 +13,7 @@
1213
*/
1314
class AllSourceRootsProvider implements FilePathReferenceProvider.SourceRootsProvider {
1415
@Override
15-
public VirtualFile[] getSourceRoots(Module module) {
16+
public VirtualFile[] getSourceRoots(Module module, ProtoPsiFileRoot psiFileRoot) {
1617
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
1718
return moduleRootManager.orderEntries().getAllSourceRoots();
1819
}

src/main/java/io/protostuff/jetbrains/plugin/reference/file/CustomIncludePathRootsProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.intellij.openapi.project.Project;
55
import com.intellij.openapi.vfs.LocalFileSystem;
66
import com.intellij.openapi.vfs.VirtualFile;
7+
import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot;
78
import io.protostuff.jetbrains.plugin.settings.ProtobufSettings;
89
import java.util.ArrayList;
910
import java.util.List;
@@ -15,7 +16,7 @@
1516
*/
1617
class CustomIncludePathRootsProvider implements FilePathReferenceProvider.SourceRootsProvider {
1718
@Override
18-
public VirtualFile[] getSourceRoots(Module module) {
19+
public VirtualFile[] getSourceRoots(Module module, ProtoPsiFileRoot psiFileRoot) {
1920
List<VirtualFile> result = new ArrayList<>();
2021
Project project = module.getProject();
2122
ProtobufSettings settings = project.getComponent(ProtobufSettings.class);

src/main/java/io/protostuff/jetbrains/plugin/reference/file/FilePathReferenceProvider.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet;
3333
import com.intellij.util.ProcessingContext;
3434
import com.intellij.util.containers.ContainerUtil;
35+
import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot;
3536
import java.util.ArrayList;
3637
import java.util.Collection;
3738
import java.util.Collections;
@@ -51,7 +52,7 @@ public class FilePathReferenceProvider extends PsiReferenceProvider {
5152
private final boolean myEndingSlashNotAllowed;
5253

5354
interface SourceRootsProvider {
54-
VirtualFile[] getSourceRoots(Module module);
55+
VirtualFile[] getSourceRoots(Module module, ProtoPsiFileRoot psiFileRoot);
5556
}
5657

5758
private List<SourceRootsProvider> sourceRootsProviders = new ArrayList<>();
@@ -66,10 +67,11 @@ public FilePathReferenceProvider(boolean endingSlashNotAllowed) {
6667
sourceRootsProviders.add(new WebCoreResourcePathRootsProvider());
6768
sourceRootsProviders.add(new CustomIncludePathRootsProvider());
6869
sourceRootsProviders.add(new LibrariesAndSdkClassesRootsProvider());
70+
sourceRootsProviders.add(new ProtoFileRelativePathRootsProvider());
6971
}
7072

7173
@NotNull
72-
public Collection<PsiFileSystemItem> getRoots(@Nullable final Module module) {
74+
public Collection<PsiFileSystemItem> getRoots(@Nullable final Module module, ProtoPsiFileRoot psiFileRoot) {
7375
if (module == null) {
7476
return Collections.emptyList();
7577
}
@@ -78,7 +80,7 @@ public Collection<PsiFileSystemItem> getRoots(@Nullable final Module module) {
7880
PsiManager psiManager = PsiManager.getInstance(module.getProject());
7981

8082
for (SourceRootsProvider sourceRootsProvider : sourceRootsProviders) {
81-
VirtualFile[] sourceRoots = sourceRootsProvider.getSourceRoots(module);
83+
VirtualFile[] sourceRoots = sourceRootsProvider.getSourceRoots(module, psiFileRoot);
8284
for (VirtualFile root : sourceRoots) {
8385
final PsiDirectory directory = psiManager.findDirectory(root);
8486
if (directory != null) {
@@ -130,12 +132,12 @@ public Collection<PsiFileSystemItem> computeDefaultContexts() {
130132
if (forModules.length > 0) {
131133
Set<PsiFileSystemItem> rootsForModules = ContainerUtil.newLinkedHashSet();
132134
for (Module forModule : forModules) {
133-
rootsForModules.addAll(getRoots(forModule));
135+
rootsForModules.addAll(getRoots(forModule, null));
134136
}
135137
return rootsForModules;
136138
}
137139

138-
return getRoots(ModuleUtilCore.findModuleForPsiElement(getElement()));
140+
return getRoots(ModuleUtilCore.findModuleForPsiElement(getElement()), null);
139141
}
140142

141143
@Override

src/main/java/io/protostuff/jetbrains/plugin/reference/file/LibrariesAndSdkClassesRootsProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.intellij.openapi.module.Module;
44
import com.intellij.openapi.roots.ModuleRootManager;
55
import com.intellij.openapi.vfs.VirtualFile;
6+
import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot;
67

78
/**
89
* Returns source roots for libraries and SDK classes.
@@ -11,7 +12,7 @@
1112
*/
1213
class LibrariesAndSdkClassesRootsProvider implements FilePathReferenceProvider.SourceRootsProvider {
1314
@Override
14-
public VirtualFile[] getSourceRoots(Module module) {
15+
public VirtualFile[] getSourceRoots(Module module, ProtoPsiFileRoot psiFileRoot) {
1516
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
1617
return moduleRootManager.orderEntries().getAllLibrariesAndSdkClassesRoots();
1718
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.protostuff.jetbrains.plugin.reference.file;
2+
3+
import com.intellij.openapi.module.Module;
4+
import com.intellij.openapi.vfs.VirtualFile;
5+
import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot;
6+
import java.util.Arrays;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
/**
11+
* Many projects are storing proto files in a locations that are not marked as source/resource root.
12+
* As a result, imports are highlighted with error "file does not exist", and all imported types are not resolvable.
13+
*
14+
* Plugin can try to resolve imports relative to source file's location using following rules:
15+
*
16+
* 1. If `package` is not set, try to look in the same folder where source file is.
17+
* 2. If package is set, try to look from parent folder.
18+
* 3. Parent folder should be computed as source file's folders, with removed corresponding parts of the package.
19+
*
20+
* For example:
21+
*
22+
* `/home/user/project/foo/bar/hello.proto`:
23+
*
24+
* ```proto
25+
* package foo.bar;
26+
* import "baz/import.proto";
27+
* ```
28+
*
29+
* Import should be resolved to `/home/user/project/baz/import.proto`.
30+
*
31+
* @author Kostiantyn Shchepanovskyi
32+
*/
33+
class ProtoFileRelativePathRootsProvider implements FilePathReferenceProvider.SourceRootsProvider {
34+
35+
private static final VirtualFile[] NONE = new VirtualFile[0];
36+
37+
@Override
38+
public VirtualFile[] getSourceRoots(Module module, @Nullable ProtoPsiFileRoot psiFileRoot) {
39+
if (psiFileRoot != null) {
40+
VirtualFile file = psiFileRoot.getVirtualFile();
41+
if (file != null) {
42+
String packageName = psiFileRoot.getPackageName();
43+
// no package - return parent dir
44+
VirtualFile dir = file.getParent();
45+
int nestingLevel = computeNestingLevel(packageName);
46+
dir = goUp(dir, nestingLevel);
47+
if (dir != null) {
48+
return toArray(dir);
49+
}
50+
}
51+
}
52+
return NONE;
53+
}
54+
55+
@Nullable
56+
private VirtualFile goUp(VirtualFile dir, int times) {
57+
while (times > 0 && dir != null) {
58+
dir = dir.getParent();
59+
times--;
60+
}
61+
return dir;
62+
}
63+
64+
private int computeNestingLevel(String packageName) {
65+
// compute nesting level
66+
int nestingLevel = 0;
67+
if (!packageName.isEmpty()) {
68+
nestingLevel++;
69+
for (char c : packageName.toCharArray()) {
70+
if (c == '.') {
71+
nestingLevel++;
72+
}
73+
}
74+
}
75+
return nestingLevel;
76+
}
77+
78+
@NotNull
79+
private VirtualFile[] toArray(VirtualFile dir) {
80+
return new VirtualFile[]{dir};
81+
}
82+
}

src/main/java/io/protostuff/jetbrains/plugin/reference/file/WebCoreResourcePathRootsProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.intellij.openapi.vfs.LocalFileSystem;
88
import com.intellij.openapi.vfs.VirtualFile;
99
import com.intellij.util.containers.MultiMap;
10+
import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot;
1011
import java.lang.reflect.Method;
1112
import java.util.ArrayList;
1213
import java.util.Collection;
@@ -47,7 +48,7 @@ class WebCoreResourcePathRootsProvider implements FilePathReferenceProvider.Sour
4748

4849
@SuppressWarnings("unchecked")
4950
@Override
50-
public VirtualFile[] getSourceRoots(Module module) {
51+
public VirtualFile[] getSourceRoots(Module module, ProtoPsiFileRoot psiFileRoot) {
5152
try {
5253
if (GET_INSTANCE != null && GET_RESOURCE_ROOTS != null) {
5354
Object configurationInstance = GET_INSTANCE.invoke(null, module.getProject());

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,27 @@ public void testEnumReference() {
3131
}
3232

3333
public void testImportedMessageReference() {
34-
checkReferenceToDataType(".reference.ImportedMessage", "reference/ImportedMessageReferenceTestData.proto", "reference/ImportedTestData.proto");
34+
checkReferenceToDataType(".reference.ImportedMessage",
35+
"reference/ImportedMessageReferenceTestData.proto",
36+
"reference/ImportedTestData.proto");
37+
}
38+
39+
public void testImportedRelativelyMessageReference() {
40+
checkReferenceToDataType(".import.ImportedMessage1",
41+
"reference/relative/import/ImportedRelativelyMessageReferenceTestData.proto",
42+
"reference/relative/import/ImportedRelativelyTestData.proto");
43+
}
44+
45+
public void testImportedRelativelyNoPackageMessageReference() {
46+
checkReferenceToDataType(".import.ImportedMessage1",
47+
"reference/relative/import/ImportedRelativelyNoPackageMessageReferenceTestData.proto",
48+
"reference/relative/import/ImportedRelativelyTestData.proto");
49+
}
50+
51+
public void testImportedRelativelyTwoLevelsMessageReference() {
52+
checkReferenceToDataType(".import.ImportedMessage1",
53+
"reference/relative/import/ImportedRelativelyTwoLevelsMessageReferenceTestData.proto",
54+
"reference/relative/import/ImportedRelativelyTestData.proto");
3555
}
3656

3757
/**
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
syntax = "proto3";
2+
3+
package import;
4+
5+
import "import/ImportedRelativelyTestData.proto";
6+
7+
message TestMessage {
8+
<caret>ImportedMessage1 message = 1;
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
syntax = "proto3";
2+
3+
import "ImportedRelativelyTestData.proto";
4+
5+
message TestMessage {
6+
import.<caret>ImportedMessage1 message = 1;
7+
}

0 commit comments

Comments
 (0)