Skip to content

Commit ad4d062

Browse files
committed
Detect GraphQL language injection using language=GraphQL injection comments (#235)
- Fixes discovery of fragment definitions in manually injected GraphQL - Enables schema discovery to run on manual injections
1 parent 7b8bf20 commit ad4d062

File tree

10 files changed

+161
-1
lines changed

10 files changed

+161
-1
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ intellij {
4242
type 'IU'
4343
updateSinceUntilBuild = false
4444
pluginName 'JS GraphQL'
45-
plugins = ['JavaScriptLanguage', 'CSS']
45+
plugins = ['JavaScriptLanguage', 'CSS', 'IntelliLang']
4646
ideaDependencyCachePath = project.buildDir
4747
}
4848

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<idea-plugin>
2+
<extensions defaultExtensionNs="com.intellij">
3+
<applicationService serviceInterface="com.intellij.lang.jsgraphql.ide.injection.GraphQLCommentBasedInjectionHelper" serviceImplementation="com.intellij.lang.jsgraphql.ide.injection.GraphQLCommentBasedInjectionHelperImpl" />
4+
</extensions>
5+
</idea-plugin>

resources/META-INF/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474

7575
<depends>com.intellij.modules.lang</depends>
7676
<depends optional="true" config-file="graphql-javascript.xml">JavaScript</depends>
77+
<depends optional="true" config-file="graphql-intellilang.xml">org.intellij.intelliLang</depends>
7778

7879
<extensionPoints>
7980
<extensionPoint name="graphQLFindUsagesFileTypeContributor" interface="com.intellij.lang.jsgraphql.ide.references.GraphQLFindUsagesFileTypeContributor" />
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) 2019-present, Jim Kynde Meyer
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
package com.intellij.lang.jsgraphql.ide.injection;
9+
10+
import com.intellij.openapi.util.Ref;
11+
import com.intellij.psi.PsiElement;
12+
import org.jetbrains.annotations.Nullable;
13+
14+
public interface GraphQLCommentBasedInjectionHelper {
15+
16+
/**
17+
* Gets whether the specified host element has a GraphQL injection based a language=GraphQL comment
18+
* @param host the host to check
19+
* @param envRef optional GraphQL environment ref to set if the host is a match
20+
* @return <code>true</code> if the host has an active GraphQL injection, <code>false</code> otherwise
21+
*/
22+
boolean isGraphQLInjectedUsingComment(PsiElement host, @Nullable Ref<String> envRef);
23+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2019-present, Jim Kynde Meyer
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
package com.intellij.lang.jsgraphql.ide.injection;
9+
10+
import com.intellij.lang.jsgraphql.GraphQLLanguage;
11+
import com.intellij.openapi.util.Ref;
12+
import com.intellij.psi.PsiElement;
13+
import org.intellij.plugins.intelliLang.inject.InjectorUtils;
14+
import org.jetbrains.annotations.Nullable;
15+
16+
/**
17+
* Detects manual GraphQL injections using IntelliLang <code>language=GraphQL</code> injection comments.
18+
*/
19+
public class GraphQLCommentBasedInjectionHelperImpl implements GraphQLCommentBasedInjectionHelper {
20+
21+
@Override
22+
public boolean isGraphQLInjectedUsingComment(PsiElement host, @Nullable Ref<String> envRef) {
23+
final InjectorUtils.CommentInjectionData injectionData = InjectorUtils.findCommentInjectionData(host, true, null);
24+
if (injectionData != null) {
25+
if (GraphQLLanguage.INSTANCE.getID().equals(injectionData.getInjectedLanguageId())) {
26+
if (envRef != null) {
27+
envRef.set("graphql");
28+
}
29+
return true;
30+
}
31+
}
32+
return false;
33+
}
34+
}

src/main/com/intellij/lang/jsgraphql/ide/injection/javascript/GraphQLLanguageInjectionUtil.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
import com.intellij.lang.javascript.psi.JSFile;
1414
import com.intellij.lang.javascript.psi.JSReferenceExpression;
1515
import com.intellij.lang.javascript.psi.ecma6.JSStringTemplateExpression;
16+
import com.intellij.lang.jsgraphql.ide.injection.GraphQLCommentBasedInjectionHelper;
1617
import com.intellij.lang.jsgraphql.psi.GraphQLFile;
18+
import com.intellij.openapi.components.ServiceManager;
1719
import com.intellij.openapi.project.Project;
1820
import com.intellij.openapi.util.Ref;
1921
import com.intellij.openapi.util.TextRange;
@@ -110,6 +112,11 @@ public static boolean isJSGraphQLLanguageInjectionTarget(PsiElement host, @Nulla
110112
return true;
111113
}
112114
}
115+
// also check for "manual" language=GraphQL injection comments
116+
final GraphQLCommentBasedInjectionHelper commentBasedInjectionHelper = ServiceManager.getService(GraphQLCommentBasedInjectionHelper.class);
117+
if (commentBasedInjectionHelper != null) {
118+
return commentBasedInjectionHelper.isGraphQLInjectedUsingComment(host, envRef);
119+
}
113120
}
114121
return false;
115122
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2019-present, Jim Kynde Meyer
3+
* All rights reserved.
4+
* <p>
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
package com.intellij.lang.jsgraphql.injection;
9+
10+
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
11+
import com.intellij.lang.annotation.HighlightSeverity;
12+
import com.intellij.lang.jsgraphql.ide.project.graphqlconfig.GraphQLConfigManager;
13+
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
14+
import org.junit.Test;
15+
16+
import java.util.List;
17+
18+
19+
public class GraphQLInjectionCodeInsightTest extends LightCodeInsightFixtureTestCase {
20+
21+
@Override
22+
public void setUp() throws Exception {
23+
super.setUp();
24+
myFixture.configureByFiles(".graphqlconfig", "schema.graphql");
25+
// use the synchronous method of building the configuration for the unit test
26+
GraphQLConfigManager.getService(getProject()).doBuildConfigurationModel(null);
27+
}
28+
29+
@Override
30+
protected String getTestDataPath() {
31+
return "test-resources/testData/graphql/injection";
32+
}
33+
34+
// ---- highlighting -----
35+
36+
@Test
37+
public void testErrorAnnotator() {
38+
myFixture.configureByFiles("injection-comment.js");
39+
final List<HighlightInfo> highlighting = myFixture.doHighlighting(HighlightSeverity.ERROR);
40+
assertEquals("Expected just one error", 1, highlighting.size());
41+
assertEquals("Unknown fragment name should be the error", "OnlyTheUnknownFragmentShouldBeHighlightedAsError", highlighting.get(0).getText());
42+
}
43+
44+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "Injection Test GraphQL Schema"
3+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const result = (
2+
// language=GraphQL
3+
{
4+
query: `
5+
6+
extend type Query {
7+
manualExtension: String
8+
}
9+
10+
fragment Login on Login {
11+
message
12+
success
13+
}
14+
15+
mutation login($email: String! $pwd: String!) {
16+
login(email: $email pwd: $pwd) {
17+
...Login
18+
...OnlyTheUnknownFragmentShouldBeHighlightedAsError
19+
}
20+
}
21+
22+
query Foo {
23+
manualExtension
24+
}
25+
`
26+
}
27+
);
28+
console.log(result);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
scalar String
2+
scalar Boolean
3+
4+
type Query {
5+
hello: String
6+
}
7+
8+
type Mutation {
9+
login(email: String, pwd: String): Login
10+
}
11+
12+
type Login {
13+
success: Boolean
14+
message: String
15+
}

0 commit comments

Comments
 (0)