Skip to content

Commit f1d945b

Browse files
committed
Handle escaped backticks in gql tagged template literals (#279)
1 parent 1181748 commit f1d945b

File tree

5 files changed

+42
-5
lines changed

5 files changed

+42
-5
lines changed

src/main/com/intellij/lang/jsgraphql/ide/GraphQLValidationAnnotator.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.intellij.lang.annotation.AnnotationHolder;
1818
import com.intellij.lang.annotation.AnnotationSession;
1919
import com.intellij.lang.annotation.Annotator;
20+
import com.intellij.lang.jsgraphql.ide.project.GraphQLInjectionSearchHelper;
2021
import com.intellij.lang.jsgraphql.ide.project.GraphQLPsiSearchHelper;
2122
import com.intellij.lang.jsgraphql.psi.GraphQLArgument;
2223
import com.intellij.lang.jsgraphql.psi.GraphQLDirective;
@@ -28,12 +29,10 @@
2829
import com.intellij.lang.jsgraphql.schema.GraphQLTypeDefinitionRegistryServiceImpl;
2930
import com.intellij.lang.jsgraphql.schema.GraphQLTypeScopeProvider;
3031
import com.intellij.lang.jsgraphql.utils.GraphQLUtil;
32+
import com.intellij.openapi.components.ServiceManager;
3133
import com.intellij.openapi.editor.Editor;
3234
import com.intellij.openapi.editor.LogicalPosition;
3335
import com.intellij.openapi.editor.colors.CodeInsightColors;
34-
import com.intellij.openapi.fileEditor.FileEditor;
35-
import com.intellij.openapi.fileEditor.FileEditorManager;
36-
import com.intellij.openapi.fileEditor.TextEditor;
3736
import com.intellij.openapi.project.Project;
3837
import com.intellij.openapi.util.Key;
3938
import com.intellij.openapi.util.Pair;
@@ -371,7 +370,14 @@ boolean isInsideTemplateElement(PsiElement psiElement) {
371370
* @return the transformed valid GraphQL as a string
372371
*/
373372
private String replacePlaceholdersWithValidGraphQL(PsiFile graphqlPsiFile) {
374-
final StringBuffer buffer = new StringBuffer(graphqlPsiFile.getText());
373+
String graphqlText = graphqlPsiFile.getText();
374+
if (graphqlPsiFile.getContext() instanceof PsiLanguageInjectionHost) {
375+
final GraphQLInjectionSearchHelper graphQLInjectionSearchHelper = ServiceManager.getService(GraphQLInjectionSearchHelper.class);
376+
if (graphQLInjectionSearchHelper != null) {
377+
graphqlText = graphQLInjectionSearchHelper.applyInjectionDelimitingQuotesEscape(graphqlText);
378+
}
379+
}
380+
final StringBuffer buffer = new StringBuffer(graphqlText);
375381
final GraphQLVisitor visitor = new GraphQLVisitor() {
376382
@Override
377383
public void visitTemplateDefinition(@NotNull GraphQLTemplateDefinition templateDefinition) {

src/main/com/intellij/lang/jsgraphql/ide/project/GraphQLInjectionSearchHelper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import com.intellij.psi.PsiElement;
1111
import com.intellij.psi.PsiFile;
12+
import com.intellij.psi.PsiLanguageInjectionHost;
1213
import com.intellij.psi.search.GlobalSearchScope;
1314

1415
import java.util.function.Consumer;
@@ -29,4 +30,12 @@ public interface GraphQLInjectionSearchHelper {
2930
*/
3031
void processInjectedGraphQLPsiFiles(PsiElement scopedElement, GlobalSearchScope schemaScope, Consumer<PsiFile> consumer);
3132

33+
/**
34+
* Inline-replaces the use of escaped string quotes which delimit GraphQL injections, e.g. an escaped backtick '\`'
35+
* in JavaScript tagged template literals, such that the injected GraphQL text represents valid GraphQL
36+
* @param rawGraphQLText the raw injected GraphQL text to escape
37+
* @return the text with injection-delimiting escaped while preserving text length and token positions, e.g. '\`' becomes ' `'
38+
* @see PsiLanguageInjectionHost#createLiteralTextEscaper()
39+
*/
40+
String applyInjectionDelimitingQuotesEscape(String rawGraphQLText);
3241
}

src/main/com/intellij/lang/jsgraphql/ide/project/javascript/GraphQLJavascriptInjectionSearchHelper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.intellij.psi.PsiRecursiveElementVisitor;
1818
import com.intellij.psi.search.GlobalSearchScope;
1919
import com.intellij.util.indexing.FileBasedIndex;
20+
import org.apache.commons.lang.StringUtils;
2021

2122
import java.util.Collections;
2223
import java.util.function.Consumer;
@@ -62,4 +63,14 @@ public void visitElement(PsiElement element) {
6263
// can't search yet (e.g. during project startup)
6364
}
6465
}
66+
67+
@Override
68+
public String applyInjectionDelimitingQuotesEscape(String rawGraphQLText) {
69+
if (rawGraphQLText != null && rawGraphQLText.contains("\\`")) {
70+
// replace escaped backticks in template literals with a whitespace and the backtick to preserve token
71+
// positions for error mappings etc.
72+
return StringUtils.replace(rawGraphQLText, "\\`", " `");
73+
}
74+
return rawGraphQLText;
75+
}
6576
}

src/main/com/intellij/lang/jsgraphql/schema/SchemaIDLTypeDefinitionRegistry.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.intellij.lang.jsgraphql.GraphQLLanguage;
1616
import com.intellij.lang.jsgraphql.endpoint.ide.project.JSGraphQLEndpointNamedTypeRegistry;
1717
import com.intellij.lang.jsgraphql.ide.editor.GraphQLIntrospectionHelper;
18+
import com.intellij.lang.jsgraphql.ide.project.GraphQLInjectionSearchHelper;
1819
import com.intellij.lang.jsgraphql.ide.project.GraphQLPsiSearchHelper;
1920
import com.intellij.lang.jsgraphql.ide.project.graphqlconfig.GraphQLConfigManager;
2021
import com.intellij.lang.jsgraphql.psi.GraphQLDirective;
@@ -60,6 +61,7 @@ public class SchemaIDLTypeDefinitionRegistry {
6061
private final PsiManager psiManager;
6162
private final JSGraphQLEndpointNamedTypeRegistry graphQLEndpointNamedTypeRegistry;
6263
private final GraphQLConfigManager graphQLConfigManager;
64+
private final GraphQLInjectionSearchHelper graphQLInjectionSearchHelper;
6365

6466
private final Map<GlobalSearchScope, TypeDefinitionRegistryWithErrors> scopeToRegistry = Maps.newConcurrentMap();
6567

@@ -75,6 +77,7 @@ public SchemaIDLTypeDefinitionRegistry(Project project) {
7577
graphQLEndpointNamedTypeRegistry = JSGraphQLEndpointNamedTypeRegistry.getService(project);
7678
graphQLPsiSearchHelper = GraphQLPsiSearchHelper.getService(project);
7779
graphQLConfigManager = GraphQLConfigManager.getService(project);
80+
graphQLInjectionSearchHelper = ServiceManager.getService(GraphQLInjectionSearchHelper.class);
7881
project.getMessageBus().connect().subscribe(GraphQLSchemaChangeListener.TOPIC, new GraphQLSchemaEventListener() {
7982
@Override
8083
public void onGraphQLSchemaChanged(Integer schemaVersion) {
@@ -172,7 +175,10 @@ public TypeDefinitionRegistryWithErrors getRegistryWithErrors(PsiElement scopedE
172175
}
173176

174177
try {
175-
final String definitionSourceText = typeSystemDefinition.getText();
178+
String definitionSourceText = typeSystemDefinition.getText();
179+
if (graphQLInjectionSearchHelper != null && psiFile.getContext() instanceof PsiLanguageInjectionHost) {
180+
definitionSourceText = graphQLInjectionSearchHelper.applyInjectionDelimitingQuotesEscape(definitionSourceText);
181+
}
176182
final StringBuffer typeSystemDefinitionBuffer = new StringBuffer(definitionPrefix.length() + definitionSourceText.length());
177183
typeSystemDefinitionBuffer.append(definitionPrefix).append(definitionSourceText);
178184
// if there are syntax errors on optional elements, replace them with whitespace

test-resources/testData/graphql/injection/lines-1/injection-source-lines-1.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@ const schema1 = gql`
1919
valueFromServer
2020
}
2121
}
22+
23+
type EscapedBacktick {
24+
"Description with escaped backtick \`"
25+
field: Foo
26+
}
2227
`;

0 commit comments

Comments
 (0)