Skip to content

Commit cffc105

Browse files
committed
shared schemas #661
1 parent f6a561f commit cffc105

File tree

17 files changed

+119
-54
lines changed

17 files changed

+119
-54
lines changed

src/main/com/intellij/lang/jsgraphql/ide/actions/GraphQLOpenConfigAction.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ class GraphQLOpenConfigAction : AnAction(
5050
return
5151
}
5252
val provider = GraphQLConfigProvider.getInstance(project)
53-
val config = provider.resolveConfig(psiFile)
53+
val config = provider.resolveProjectConfig(psiFile)
5454
// look for the closest one as a fallback for cases when a file is excluded
5555
// and not matched by the `resolveConfig` call
56-
val configFile = config?.file ?: provider.findClosestConfig(psiFile)?.file
56+
val configFile = config?.file ?: provider.findConfig(psiFile)?.config?.file
5757
if (configFile != null) {
5858
val fileEditorManager = FileEditorManager.getInstance(project)
5959
fileEditorManager.openFile(configFile, true, true)

src/main/com/intellij/lang/jsgraphql/ide/config/GraphQLConfigProvider.kt

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class GraphQLConfigProvider(private val project: Project) : Disposable, Modifica
6363
private val LOG = logger<GraphQLConfigProvider>()
6464

6565
private val CONFIG_CLOSEST =
66-
Key.create<CachedValue<GraphQLConfig?>>("graphql.config.closest")
66+
Key.create<CachedValue<GraphQLConfigSearchResult?>>("graphql.config.closest")
6767
private val CONFIG_OVERRIDE_FILE_KEY =
6868
Key.create<CachedValue<GraphQLConfigOverride?>>("graphql.config.override.file")
6969
private val CONFIG_OVERRIDE_PATH_KEY =
@@ -127,29 +127,23 @@ class GraphQLConfigProvider(private val project: Project) : Disposable, Modifica
127127
}
128128

129129
@RequiresReadLock
130-
fun resolveConfig(context: PsiFile): GraphQLProjectConfig? {
130+
fun resolveProjectConfig(context: PsiFile): GraphQLProjectConfig? {
131131
// shouldn't try resolving for the config file itself
132-
if (getPhysicalVirtualFile(context)?.name in CONFIG_NAMES) {
132+
if (isConfigFile(context)) {
133133
return null
134134
}
135135

136-
val overriddenConfig = findOverriddenConfig(context)
137-
if (overriddenConfig != null) {
138-
val config = getForConfigFile(overriddenConfig.file)
139-
if (config != null) {
140-
val projectConfig = config.findProject(overriddenConfig.projectName)
141-
if (projectConfig != null) {
142-
return projectConfig
143-
}
144-
}
136+
val searchResult = findConfig(context)
137+
return when {
138+
searchResult == null -> null
139+
searchResult.projectName != null -> searchResult.config.findProject(searchResult.projectName)
140+
else -> searchResult.config.match(context)
145141
}
146-
147-
return findClosestConfig(context)?.match(context)
148142
}
149143

150144
@RequiresReadLock
151-
fun resolveConfig(virtualFile: VirtualFile): GraphQLProjectConfig? =
152-
PsiManager.getInstance(project).findFile(virtualFile)?.let { resolveConfig(it) }
145+
fun resolveProjectConfig(virtualFile: VirtualFile): GraphQLProjectConfig? =
146+
PsiManager.getInstance(project).findFile(virtualFile)?.let { resolveProjectConfig(it) }
153147

154148
fun getForConfigFile(file: VirtualFile?): GraphQLConfig? {
155149
return when {
@@ -239,17 +233,30 @@ class GraphQLConfigProvider(private val project: Project) : Disposable, Modifica
239233
}
240234

241235
@RequiresReadLock
242-
fun findClosestConfig(context: PsiFile): GraphQLConfig? {
236+
fun findConfig(context: PsiFile): GraphQLConfigSearchResult? {
243237
return CachedValuesManager.getCachedValue(context, CONFIG_CLOSEST) {
238+
val overriddenConfig = findOverriddenConfig(context)
239+
if (overriddenConfig != null) {
240+
val config = getForConfigFile(overriddenConfig.file)
241+
if (config != null) {
242+
return@getCachedValue CachedValueProvider.Result.create(
243+
GraphQLConfigSearchResult(config, overriddenConfig.projectName ?: GraphQLConfig.DEFAULT_PROJECT),
244+
scopeDependency,
245+
)
246+
}
247+
}
248+
244249
var from: VirtualFile? = getPhysicalVirtualFile(context)
245250

246251
val sourceFile = generatedSourcesManager.getSourceFile(from) ?: remoteSchemasRegistry.getSourceFile(from)
247252
if (sourceFile != null) {
248253
from = sourceFile
249254
}
250255

251-
val config = findConfigInParents(from)
252-
CachedValueProvider.Result.create(config, scopeDependency)
256+
CachedValueProvider.Result.create(
257+
findConfigInParents(from)?.let { GraphQLConfigSearchResult(it) },
258+
scopeDependency,
259+
)
253260
}
254261
}
255262

@@ -500,4 +507,6 @@ class GraphQLConfigProvider(private val project: Project) : Disposable, Modifica
500507
data class ConfigEvaluationState(val status: GraphQLConfigEvaluationStatus, val error: Throwable?)
501508
}
502509

510+
class GraphQLConfigSearchResult(val config: GraphQLConfig, val projectName: String? = null)
511+
503512
private data class GraphQLConfigOverride(val file: VirtualFile, val projectName: String?)

src/main/com/intellij/lang/jsgraphql/ide/config/GraphQLConfigUtil.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
package com.intellij.lang.jsgraphql.ide.config
44

55
import com.intellij.lang.jsgraphql.asSafely
6+
import com.intellij.lang.jsgraphql.psi.getPhysicalVirtualFile
67
import com.intellij.openapi.vfs.VirtualFile
8+
import com.intellij.psi.PsiFile
79

810
fun isLegacyConfig(file: VirtualFile?): Boolean {
911
return isLegacyConfig(file?.name)
@@ -13,6 +15,8 @@ fun isLegacyConfig(filename: String?): Boolean {
1315
return filename?.lowercase() in LEGACY_CONFIG_NAMES
1416
}
1517

18+
fun isConfigFile(context: PsiFile) = getPhysicalVirtualFile(context)?.name in CONFIG_NAMES
19+
1620
fun parseMap(value: Any?): Map<String, Any?>? =
1721
value.asSafely<Map<*, *>>()?.mapNotNull {
1822
val key = it.key as? String ?: return@mapNotNull null

src/main/com/intellij/lang/jsgraphql/ide/config/env/GraphQLEditEnvironmentVariablesAction.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class GraphQLEditEnvironmentVariablesAction : AnAction(
2424
val environment = if (virtualFile.name in CONFIG_NAMES) {
2525
provider.getForConfigFile(virtualFile)?.environment
2626
} else if (GraphQLFileType.isGraphQLFile(project, virtualFile) && inToolbar) {
27-
provider.resolveConfig(virtualFile)?.parentConfig?.environment
27+
provider.resolveProjectConfig(virtualFile)?.rootConfig?.environment
2828
} else {
2929
null
3030
}
@@ -55,7 +55,7 @@ class GraphQLEditEnvironmentVariablesAction : AnAction(
5555
val environment = if (virtualFile.name in CONFIG_NAMES) {
5656
provider.getForConfigFile(virtualFile)?.environment
5757
} else {
58-
provider.resolveConfig(virtualFile)?.parentConfig?.environment
58+
provider.resolveProjectConfig(virtualFile)?.rootConfig?.environment
5959
} ?: return
6060

6161
GraphQLEnvironmentVariablesDialog(project, environment, virtualFile, false).show()

src/main/com/intellij/lang/jsgraphql/ide/config/model/GraphQLConfig.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ data class GraphQLConfig(
4545

4646
private val projects: Map<String, GraphQLProjectConfig> = initProjects()
4747

48+
/**
49+
* A first project config without `include` and `exclude` specified.
50+
*/
51+
val fallback: GraphQLProjectConfig? = findFallbackConfig()
52+
4853
/**
4954
* NULL config to store as weak referenced value in [fileToProjectCache]. Shouldn't be exposed to the outside of the class.
5055
*/
@@ -75,6 +80,15 @@ data class GraphQLConfig(
7580
}
7681
}
7782

83+
private fun findFallbackConfig(): GraphQLProjectConfig? {
84+
for (config in projects.values) {
85+
if (config.include.isEmpty() && config.exclude.isEmpty()) {
86+
return config
87+
}
88+
}
89+
return null
90+
}
91+
7892
fun findProject(name: String? = null): GraphQLProjectConfig? {
7993
return if (name == null) getDefault() else projects[name]
8094
}
@@ -127,16 +141,10 @@ data class GraphQLConfig(
127141
}
128142
}
129143

130-
for (config in projects.values) {
131-
if (config.include.isEmpty() && config.exclude.isEmpty()) {
132-
return config
133-
}
134-
}
135-
136-
return null
144+
return fallback
137145
}
138146

139-
private fun requiresSchemaStrictMatch(virtualFile: VirtualFile) =
147+
fun requiresSchemaStrictMatch(virtualFile: VirtualFile) =
140148
virtualFile.fileType == JsonFileType.INSTANCE ||
141149
generatedSourcesManager.isGeneratedFile(virtualFile) ||
142150
generatedSourcesManager.isSourceForGeneratedFile(virtualFile) ||

src/main/com/intellij/lang/jsgraphql/ide/config/model/GraphQLProjectConfig.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ class GraphQLProjectConfig(
3535
val rawData: GraphQLRawProjectConfig,
3636
val defaultData: GraphQLRawProjectConfig?,
3737
val environment: GraphQLEnvironmentSnapshot,
38-
val parentConfig: GraphQLConfig,
38+
val rootConfig: GraphQLConfig,
3939
) {
4040
private val generatedSourcesManager = GraphQLGeneratedSourcesManager.getInstance(project)
4141
private val remoteSchemasRegistry = GraphQLRemoteSchemasRegistry.getInstance(project)
4242

43-
val dir: VirtualFile = parentConfig.dir
43+
val dir: VirtualFile = rootConfig.dir
4444

45-
val file: VirtualFile? = parentConfig.file
45+
val file: VirtualFile? = rootConfig.file
4646

47-
val isRootEmpty: Boolean = parentConfig.isEmpty
47+
val isRootEmpty: Boolean = rootConfig.isEmpty
4848

4949
val isLegacy = isLegacyConfig(file)
5050

src/main/com/intellij/lang/jsgraphql/ide/config/scope/GraphQLConfigSchemaScope.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ class GraphQLConfigSchemaScope(
1515
if (!super.match(file)) {
1616
return false
1717
}
18-
if (config.isDefault && config.isRootEmpty) {
18+
if (projectConfig.isDefault && projectConfig.isRootEmpty) {
1919
return true
2020
}
21-
return config.matchesSchema(file)
21+
return projectConfig.matchesSchema(file)
2222
}
2323
}

src/main/com/intellij/lang/jsgraphql/ide/config/scope/GraphQLConfigScope.kt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ import com.intellij.lang.jsgraphql.ide.config.GraphQLConfigProvider
44
import com.intellij.lang.jsgraphql.ide.config.model.GraphQLProjectConfig
55
import com.intellij.openapi.project.Project
66
import com.intellij.openapi.vfs.VirtualFile
7+
import com.intellij.psi.PsiManager
78
import com.intellij.psi.search.DelegatingGlobalSearchScope
89
import com.intellij.psi.search.GlobalSearchScope
910
import com.intellij.psi.util.PsiModificationTracker
1011

1112
open class GraphQLConfigScope(
1213
project: Project,
1314
baseScope: GlobalSearchScope,
14-
protected val config: GraphQLProjectConfig
15-
) : DelegatingGlobalSearchScope(baseScope, config) {
15+
protected val projectConfig: GraphQLProjectConfig,
16+
) : DelegatingGlobalSearchScope(baseScope, projectConfig) {
1617

18+
private val psiManager = PsiManager.getInstance(project)
1719
private val configProvider = GraphQLConfigProvider.getInstance(project)
1820

1921
private val matchingFiles =
@@ -28,13 +30,20 @@ open class GraphQLConfigScope(
2830
}
2931

3032
protected open fun match(file: VirtualFile): Boolean {
31-
// The matching logic considers both glob patterns and specific corner cases,
32-
// such as utilizing the first project with empty `include` and `exclude` arrays
33-
// in the absence of an exact match or using a default project if the whole config is empty.
34-
//
35-
// Scope checks will be evaluated against the whole project,
36-
// so we need to run a complete matching algorithm, including a search for the nearest config file.
37-
// Otherwise, it's possible to include a random file from non-related subdirectory, e.g. when we have an empty config.
38-
return configProvider.resolveConfig(file) == config
33+
val psiFile = psiManager.findFile(file) ?: return false
34+
val matchingConfig = configProvider.resolveProjectConfig(psiFile) ?: return false
35+
if (projectConfig == matchingConfig) {
36+
return true
37+
}
38+
if (projectConfig.rootConfig != matchingConfig.rootConfig) {
39+
return false
40+
}
41+
// a resolved config could be just a first one matching or even a fallback,
42+
// so to support shared files between projects we need to match them manually
43+
return if (projectConfig.rootConfig.requiresSchemaStrictMatch(file)) {
44+
projectConfig.matchesSchema(psiFile)
45+
} else {
46+
projectConfig.matches(psiFile)
47+
}
3948
}
4049
}

src/main/com/intellij/lang/jsgraphql/ide/resolve/GraphQLScopeProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class GraphQLScopeProvider(private val project: Project) {
9292
): GlobalSearchScope {
9393
val file = element.containingFile
9494
return CachedValuesManager.getCachedValue(file, key) {
95-
val projectConfig = configProvider.resolveConfig(file)
95+
val projectConfig = configProvider.resolveProjectConfig(file)
9696
var scope: GlobalSearchScope =
9797
projectConfig?.let { if (key == STRICT_SCOPE_KEY) it.schemaScope else it.scope }
9898
?: globalScope.takeUnless { configProvider.hasExplicitConfiguration }

src/main/com/intellij/lang/jsgraphql/ui/GraphQLUIProjectService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ private void reloadEndpoints() {
198198

199199
for (VirtualFile file : files) {
200200
List<GraphQLConfigEndpoint> endpoints = ReadAction.compute(() -> {
201-
GraphQLProjectConfig config = configProvider.resolveConfig(file);
201+
GraphQLProjectConfig config = configProvider.resolveProjectConfig(file);
202202
return config != null ? config.getEndpoints() : Collections.emptyList();
203203
});
204204

@@ -254,7 +254,7 @@ private static class GraphQLEditorHeaderComponent extends EditorHeaderComponent
254254
}
255255

256256
private JComponent createEditorHeaderComponent(@NotNull FileEditor fileEditor, @NotNull Editor editor, @NotNull VirtualFile file) {
257-
GraphQLProjectConfig config = ReadAction.compute(() -> GraphQLConfigProvider.getInstance(myProject).resolveConfig(file));
257+
GraphQLProjectConfig config = ReadAction.compute(() -> GraphQLConfigProvider.getInstance(myProject).resolveProjectConfig(file));
258258
List<GraphQLConfigEndpoint> endpoints = config != null ? config.getEndpoints() : Collections.emptyList();
259259

260260
final GraphQLEditorHeaderComponent headerComponent = new GraphQLEditorHeaderComponent();

0 commit comments

Comments
 (0)