Skip to content

Commit

Permalink
Support for gql tagged templates used by Apollo and Lokka GraphQL Cli…
Browse files Browse the repository at this point in the history
…ents (#25)
  • Loading branch information
jimkyndemeyer committed Sep 11, 2016
1 parent ef5bed3 commit 2ac5950
Show file tree
Hide file tree
Showing 22 changed files with 290 additions and 68 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## 1.4.1 (2016-09-11)

Features:

- Support for gql tagged templates used by Apollo and Lokka GraphQL Clients (#25)
- Language Service 1.3.0 with Lokka and Apollo gql support (#25)
- Persist endpoint selection to project configuration

Changes:

- Fixes false Error in Relay Mutation (#23)


## 1.4.0 (2016-08-28)

Features:
Expand Down
4 changes: 3 additions & 1 deletion resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<idea-plugin version="2">
<id>com.intellij.lang.jsgraphql</id>
<name>JS GraphQL</name>
<version>1.4.0</version>
<version>1.4.1</version>
<vendor>Jim Kynde Meyer - [email protected]</vendor>

<description><![CDATA[
Expand All @@ -29,6 +29,7 @@

<change-notes><![CDATA[
<ul>
<li>1.4.1: Support for gql tagged templates used by Apollo and Lokka GraphQL Clients. Fixes false Error in Relay Mutation.</li>
<li>1.4.0: Language Service 1.2.0 based on graphql 0.7.0 and codemirror-graphql 0.5.4. Basic editor support for GraphQL Schema (.graphqls)</li>
<li>1.3.3: Fixes compatibility issue with IDEA 2016.2.2</li>
<li>1.3.2: Removes GraphQL schema from scratch file formats.</li>
Expand Down Expand Up @@ -132,6 +133,7 @@
<!-- Editor notification bars and markers -->
<editorNotificationProvider implementation="com.intellij.lang.jsgraphql.ide.notifications.JSGraphQLNodeInterpreterEditorNotificationProvider"/>
<editorNotificationProvider implementation="com.intellij.lang.jsgraphql.ide.notifications.JSGraphQLConfigEditorNotificationProvider"/>
<editorNotificationProvider implementation="com.intellij.lang.jsgraphql.ide.notifications.JSGraphQLApolloLokkaEditorNotificationProvider"/>
<codeInsight.lineMarkerProvider language="JavaScript" implementationClass="com.intellij.lang.jsgraphql.ide.editor.JSGraphQLLineMarkerProvider" />

<editorTabColorProvider implementation="com.intellij.lang.jsgraphql.schema.ide.project.JSGraphQLSchemaEditorTabColorProvider" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public class JSGraphQLIcons {
public static class Logos {
public static final Icon GraphQL = JSGraphQLIcons.load("/com/intellij/lang/jsgraphql/icons/graphql.png");
public static final Icon Relay = JSGraphQLIcons.load("/com/intellij/lang/jsgraphql/icons/relay.png");
public static final Icon Apollo = JSGraphQLIcons.load("/com/intellij/lang/jsgraphql/icons/apollo.png");
public static final Icon Lokka = JSGraphQLIcons.load("/com/intellij/lang/jsgraphql/icons/lokka.png");
}

public static class Files {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public JSGraphQLAnnotationResult collectInformation(@NotNull PsiFile file, @NotN
buffer = getWhitespacePaddedGraphQL(file, buffer);
}
if (buffer.length() > 0) {
final boolean relay = JSGraphQLLanguageInjectionUtil.isRelayInjection(file);
final AnnotationsResponse annotations = JSGraphQLNodeLanguageServiceClient.getAnnotations(buffer.toString(), file.getProject(), relay);
final String environment = JSGraphQLLanguageInjectionUtil.getEnvironment(file);
final AnnotationsResponse annotations = JSGraphQLNodeLanguageServiceClient.getAnnotations(buffer.toString(), file.getProject(), environment);
return new JSGraphQLAnnotationResult(annotations, editor);
}
} else if(file instanceof JSGraphQLSchemaFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ public JSGraphQLCompletionContributor() {
@Override
protected void addCompletions(@NotNull final CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {

final boolean relay = JSGraphQLLanguageInjectionUtil.isRelayInjection(parameters.getOriginalFile());
final String environment = JSGraphQLLanguageInjectionUtil.getEnvironment(parameters.getOriginalFile());
final String buffer = parameters.getOriginalFile().getText();
final Editor editor = parameters.getEditor();
final Project project = editor.getProject();
final LogicalPosition logicalPosition = editor.offsetToLogicalPosition(parameters.getOffset());

final HintsResponse hints = JSGraphQLNodeLanguageServiceClient.getHints(buffer, logicalPosition.line, logicalPosition.column, project, relay);
final HintsResponse hints = JSGraphQLNodeLanguageServiceClient.getHints(buffer, logicalPosition.line, logicalPosition.column, project, environment);

if(hints != null) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ private String createQuickNavigateDocumentation(PsiElement element, boolean full
// hence our reference to the file -- see JSGraphQLSchemaLanguageProjectService.getReference()
final String buffer = element.getContainingFile().getText();
final LogicalPosition pos = getTokenPos(buffer, element);
final boolean relay = JSGraphQLLanguageInjectionUtil.isRelayInjection(element.getContainingFile());
final String environment = JSGraphQLLanguageInjectionUtil.getEnvironment(element.getContainingFile());
final TokenDocumentationResponse tokenDocumentation = JSGraphQLNodeLanguageServiceClient.getTokenDocumentation(
buffer,
pos.line,
pos.column,
project,
relay
environment
);
if(tokenDocumentation != null) {
String doc = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.ui.EditorNotifications;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -28,9 +29,9 @@ public class JSGraphQLLineMarkerProvider implements LineMarkerProvider {
@Nullable
@Override
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
Ref<JSGraphQLLanguageInjectionUtil.JSGraphQLInjectionTag> tagRef = new Ref<>();
if(JSGraphQLLanguageInjectionUtil.isJSGraphQLLanguageInjectionTarget(element, tagRef)) {
return createLineMarkerInfo(element, tagRef);
final Ref<String> envRef = new Ref<>();
if(JSGraphQLLanguageInjectionUtil.isJSGraphQLLanguageInjectionTarget(element, envRef)) {
return createLineMarkerInfo(element, envRef);
}
return null;
}
Expand All @@ -40,10 +41,37 @@ public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull

}

private LineMarkerInfo createLineMarkerInfo(PsiElement element, Ref<JSGraphQLLanguageInjectionUtil.JSGraphQLInjectionTag> tagRef) {
final Icon icon = tagRef.get() == JSGraphQLLanguageInjectionUtil.JSGraphQLInjectionTag.RelayQL ? JSGraphQLIcons.Logos.Relay : JSGraphQLIcons.Logos.GraphQL;
return new LineMarkerInfo<>(element, element.getTextRange(), icon, Pass.UPDATE_ALL, o -> "Relay GraphQL Template", (e, elt) -> {
BrowserUtil.browse("https://facebook.github.io/relay/docs/api-reference-relay-ql.html");
private LineMarkerInfo createLineMarkerInfo(PsiElement element, Ref<String> envRef) {
final Icon icon;
final String tooltip;
final String url;
boolean configureGQL = false;
if(JSGraphQLLanguageInjectionUtil.RELAY_ENVIRONMENT.equals(envRef.get())) {
icon = JSGraphQLIcons.Logos.Relay;
tooltip = "Relay GraphQL Template";
url = "https://facebook.github.io/relay/docs/api-reference-relay-ql.html";
} else if(JSGraphQLLanguageInjectionUtil.GRAPHQL_ENVIRONMENT.equals(envRef.get())) {
icon = JSGraphQLIcons.Logos.GraphQL;
tooltip = "GraphQL";
url = "http://graphql.org/";
} else if(JSGraphQLLanguageInjectionUtil.APOLLO_ENVIRONMENT.equals(envRef.get())) {
icon = JSGraphQLIcons.Logos.Apollo;
tooltip = "Apollo Client GraphQL Template";
url = "http://docs.apollostack.com/apollo-client/core.html";
configureGQL = true;
} else if(JSGraphQLLanguageInjectionUtil.LOKKA_ENVIRONMENT.equals(envRef.get())) {
icon = JSGraphQLIcons.Logos.Lokka;
tooltip = "Lokka GraphQL Template";
url = "https://github.com/kadirahq/lokka";
configureGQL = true;
} else {
return null;
}
if(configureGQL && !JSGraphQLLanguageInjectionUtil.isGQLEnvironmentConfigured(element.getProject())) {
EditorNotifications.getInstance(element.getProject()).updateNotifications(element.getContainingFile().getVirtualFile());
}
return new LineMarkerInfo<>(element, element.getTextRange(), icon, Pass.UPDATE_ALL, o -> tooltip, (e, elt) -> {
BrowserUtil.browse(url);
}, GutterIconRenderer.Alignment.CENTER);
}
}
Original file line number Diff line number Diff line change
@@ -1,58 +1,91 @@
/**
* Copyright (c) 2015-present, Jim Kynde Meyer
* All rights reserved.
* Copyright (c) 2015-present, Jim Kynde Meyer
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.intellij.lang.jsgraphql.ide.injection;

import com.google.common.collect.Sets;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.lang.javascript.psi.JSFile;
import com.intellij.lang.javascript.psi.JSReferenceExpression;
import com.intellij.lang.javascript.psi.ecma6.JSStringTemplateExpression;
import com.intellij.lang.jsgraphql.psi.JSGraphQLFile;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.PsiRecursiveElementVisitor;
import org.jetbrains.annotations.Nullable;

import java.util.Set;

public class JSGraphQLLanguageInjectionUtil {

public static final String RELAY_QL_TEMPLATE_TAG = "Relay.QL";
public static final String GRAPHQL_TEMPLATE_TAG = "graphql";
public static final String APOLLO_GQL_TEMPLATE_TAG = "gql";
public static final String GQL_TEMPLATE_TAG = "gql";

public enum JSGraphQLInjectionTag {
RelayQL,
GraphQL
}
public final static Set<String> SUPPORTED_TAG_NAMES = Sets.newHashSet(
RELAY_QL_TEMPLATE_TAG,
GRAPHQL_TEMPLATE_TAG,
GQL_TEMPLATE_TAG
);


public static final String GRAPHQL_ENVIRONMENT = "graphql";
public static final String RELAY_ENVIRONMENT = "relay";
public static final String APOLLO_ENVIRONMENT = "apollo";
public static final String LOKKA_ENVIRONMENT = "lokka";
public static final String DEFAULT_GQL_ENVIRONMENT = APOLLO_ENVIRONMENT;

private static final String PROJECT_GQL_ENV = JSGraphQLLanguageInjectionUtil.class.getName() + ".gql";

public static boolean isJSGraphQLLanguageInjectionTarget(PsiElement host) {
return isJSGraphQLLanguageInjectionTarget(host, null);
}

public static boolean isRelayInjection(PsiFile file) {
if(file instanceof JSFile) {
return true;
public static String getEnvironment(PsiFile file) {
if (file instanceof JSFile) {
// for JS Files we have to check the kind of environment being used
final Ref<String> envRef = new Ref<>();
file.accept(new PsiRecursiveElementVisitor() {
@Override
public void visitElement(PsiElement element) {
if (!isJSGraphQLLanguageInjectionTarget(element, envRef)) {
// no match yet, so keep visiting
super.visitElement(element);
}
}
});
final String environment = envRef.get();
if (environment != null) {
return environment;
}
} else if (file instanceof JSGraphQLFile) {
final Ref<JSGraphQLInjectionTag> tag = new Ref<>();
return file.getContext() != null && isJSGraphQLLanguageInjectionTarget(file.getContext(), tag) && tag.get() == JSGraphQLInjectionTag.RelayQL;
final Ref<String> tag = new Ref<>();
if (file.getContext() != null && isJSGraphQLLanguageInjectionTarget(file.getContext(), tag)) {
return tag.get();
}
}
return false;
// fallback is traditional GraphQL
return GRAPHQL_ENVIRONMENT;
}

public static boolean isJSGraphQLLanguageInjectionTarget(PsiElement host, @Nullable Ref<JSGraphQLInjectionTag> tagRef) {
if(host instanceof JSStringTemplateExpression && host instanceof PsiLanguageInjectionHost) {
public static boolean isJSGraphQLLanguageInjectionTarget(PsiElement host, @Nullable Ref<String> envRef) {
if (host instanceof JSStringTemplateExpression && host instanceof PsiLanguageInjectionHost) {
JSStringTemplateExpression template = (JSStringTemplateExpression) host;
// check if we're a Relay.QL or graphql tagged template
final PsiElement firstChild = template.getFirstChild();
if (firstChild instanceof JSReferenceExpression) {
final String tagText = firstChild.getText();
if (RELAY_QL_TEMPLATE_TAG.equals(tagText) || GRAPHQL_TEMPLATE_TAG.equals(tagText) || APOLLO_GQL_TEMPLATE_TAG.equals(tagText)) {
if(tagRef != null) {
tagRef.set(RELAY_QL_TEMPLATE_TAG.equals(tagText) ? JSGraphQLInjectionTag.RelayQL : JSGraphQLInjectionTag.GraphQL);
if (SUPPORTED_TAG_NAMES.contains(tagText)) {
if (envRef != null) {
envRef.set(getEnvironmentFromTemplateTag(tagText, host));
}
return true;
}
Expand All @@ -61,12 +94,34 @@ public static boolean isJSGraphQLLanguageInjectionTarget(PsiElement host, @Nulla
return false;
}

public static String getEnvironmentFromTemplateTag(String tagText, PsiElement host) {
if (RELAY_QL_TEMPLATE_TAG.equals(tagText)) {
return RELAY_ENVIRONMENT;
}
if (GRAPHQL_TEMPLATE_TAG.equals(tagText)) {
return GRAPHQL_ENVIRONMENT;
}
if (GQL_TEMPLATE_TAG.equals(tagText)) {
return PropertiesComponent.getInstance(host.getProject()).getValue(PROJECT_GQL_ENV, DEFAULT_GQL_ENVIRONMENT);
}
// fallback
return GRAPHQL_ENVIRONMENT;
}

public static boolean isGQLEnvironmentConfigured(Project project) {
return !"".equals(PropertiesComponent.getInstance(project).getValue(PROJECT_GQL_ENV, ""));
}

public static void setGQLEnvironment(Project project, String env) {
PropertiesComponent.getInstance(project).setValue(PROJECT_GQL_ENV, env);
}

public static TextRange getGraphQLTextRange(JSStringTemplateExpression template) {
int start = 0;
int end = 0;
final TextRange[] stringRanges = template.getStringRanges();
for (TextRange textRange : stringRanges) {
if(start == 0) {
if (start == 0) {
start = textRange.getStartOffset();
}
end = textRange.getEndOffset();
Expand Down
Loading

0 comments on commit 2ac5950

Please sign in to comment.