UNPKG

7.87 kBPlain TextView Raw
1import { parse, Source, DocumentNode } from "graphql";
2import { SourceLocation, getLocation } from "graphql/language/location";
3
4import {
5 TextDocument,
6 Position,
7 Diagnostic,
8 DiagnosticSeverity,
9} from "vscode-languageserver";
10
11import { getRange as rangeOfTokenAtLocation } from "@apollographql/graphql-language-service-interface/dist/getDiagnostics";
12
13import {
14 positionFromSourceLocation,
15 rangeInContainingDocument,
16} from "./utilities/source";
17
18export class GraphQLDocument {
19 ast?: DocumentNode;
20 syntaxErrors: Diagnostic[] = [];
21
22 constructor(public source: Source) {
23 try {
24 this.ast = parse(source);
25 } catch (error) {
26 // Don't add syntax errors when GraphQL has been commented out
27 if (maybeCommentedOut(source.body)) return;
28
29 // A GraphQL syntax error only has a location and no node, because we don't have an AST
30 // So we use the online parser to get the range of the token at that location
31 const range = rangeInContainingDocument(
32 source,
33 rangeOfTokenAtLocation(error.locations[0], source.body)
34 );
35 this.syntaxErrors.push({
36 severity: DiagnosticSeverity.Error,
37 message: error.message,
38 source: "GraphQL: Syntax",
39 range,
40 });
41 }
42 }
43
44 containsPosition(position: Position): boolean {
45 if (position.line < this.source.locationOffset.line - 1) return false;
46 const end = positionFromSourceLocation(
47 this.source,
48 getLocation(this.source, this.source.body.length)
49 );
50 return position.line <= end.line;
51 }
52}
53
54export function extractGraphQLDocuments(
55 document: TextDocument,
56 tagName: string = "gql"
57): GraphQLDocument[] | null {
58 switch (document.languageId) {
59 case "graphql":
60 return [
61 new GraphQLDocument(new Source(document.getText(), document.uri)),
62 ];
63 case "javascript":
64 case "javascriptreact":
65 case "typescript":
66 case "typescriptreact":
67 case "vue":
68 return extractGraphQLDocumentsFromJSTemplateLiterals(document, tagName);
69 case "python":
70 return extractGraphQLDocumentsFromPythonStrings(document, tagName);
71 case "ruby":
72 return extractGraphQLDocumentsFromRubyStrings(document, tagName);
73 case "dart":
74 return extractGraphQLDocumentsFromDartStrings(document, tagName);
75 case "reason":
76 return extractGraphQLDocumentsFromReasonStrings(document, tagName);
77 case "elixir":
78 return extractGraphQLDocumentsFromElixirStrings(document, tagName);
79 default:
80 return null;
81 }
82}
83
84function extractGraphQLDocumentsFromJSTemplateLiterals(
85 document: TextDocument,
86 tagName: string
87): GraphQLDocument[] | null {
88 const text = document.getText();
89
90 const documents: GraphQLDocument[] = [];
91
92 const regExp = new RegExp(`${tagName}\\s*\`([\\s\\S]+?)\``, "gm");
93
94 let result;
95 while ((result = regExp.exec(text)) !== null) {
96 const contents = replacePlaceholdersWithWhiteSpace(result[1]);
97 const position = document.positionAt(result.index + (tagName.length + 1));
98 const locationOffset: SourceLocation = {
99 line: position.line + 1,
100 column: position.character + 1,
101 };
102 const source = new Source(contents, document.uri, locationOffset);
103 documents.push(new GraphQLDocument(source));
104 }
105
106 if (documents.length < 1) return null;
107
108 return documents;
109}
110
111function extractGraphQLDocumentsFromPythonStrings(
112 document: TextDocument,
113 tagName: string
114): GraphQLDocument[] | null {
115 const text = document.getText();
116
117 const documents: GraphQLDocument[] = [];
118
119 const regExp = new RegExp(
120 `\\b(${tagName}\\s*\\(\\s*[bfru]*("(?:"")?|'(?:'')?))([\\s\\S]+?)\\2\\s*\\)`,
121 "gm"
122 );
123
124 let result;
125 while ((result = regExp.exec(text)) !== null) {
126 const contents = replacePlaceholdersWithWhiteSpace(result[3]);
127 const position = document.positionAt(result.index + result[1].length);
128 const locationOffset: SourceLocation = {
129 line: position.line + 1,
130 column: position.character + 1,
131 };
132 const source = new Source(contents, document.uri, locationOffset);
133 documents.push(new GraphQLDocument(source));
134 }
135
136 if (documents.length < 1) return null;
137
138 return documents;
139}
140
141function extractGraphQLDocumentsFromRubyStrings(
142 document: TextDocument,
143 tagName: string
144): GraphQLDocument[] | null {
145 const text = document.getText();
146
147 const documents: GraphQLDocument[] = [];
148
149 const regExp = new RegExp(`(<<-${tagName})([\\s\\S]+?)${tagName}`, "gm");
150
151 let result;
152 while ((result = regExp.exec(text)) !== null) {
153 const contents = replacePlaceholdersWithWhiteSpace(result[2]);
154 const position = document.positionAt(result.index + result[1].length);
155 const locationOffset: SourceLocation = {
156 line: position.line + 1,
157 column: position.character + 1,
158 };
159 const source = new Source(contents, document.uri, locationOffset);
160 documents.push(new GraphQLDocument(source));
161 }
162
163 if (documents.length < 1) return null;
164
165 return documents;
166}
167
168function extractGraphQLDocumentsFromDartStrings(
169 document: TextDocument,
170 tagName: string
171): GraphQLDocument[] | null {
172 const text = document.getText();
173
174 const documents: GraphQLDocument[] = [];
175
176 const regExp = new RegExp(
177 `\\b(${tagName}\\(\\s*r?("""|'''))([\\s\\S]+?)\\2\\s*\\)`,
178 "gm"
179 );
180
181 let result;
182 while ((result = regExp.exec(text)) !== null) {
183 const contents = replacePlaceholdersWithWhiteSpace(result[3]);
184 const position = document.positionAt(result.index + result[1].length);
185 const locationOffset: SourceLocation = {
186 line: position.line + 1,
187 column: position.character + 1,
188 };
189 const source = new Source(contents, document.uri, locationOffset);
190 documents.push(new GraphQLDocument(source));
191 }
192
193 if (documents.length < 1) return null;
194
195 return documents;
196}
197
198function extractGraphQLDocumentsFromReasonStrings(
199 document: TextDocument,
200 tagName: string
201): GraphQLDocument[] | null {
202 const text = document.getText();
203
204 const documents: GraphQLDocument[] = [];
205
206 const reasonFileFilter = new RegExp(/(\[%(graphql|relay\.))/g);
207
208 if (!reasonFileFilter.test(text)) {
209 return documents;
210 }
211
212 const reasonRegexp = new RegExp(
213 /(?<=\[%(graphql|relay\.\w*)[\s\S]*{\|)[.\s\S]+?(?=\|})/gm
214 );
215
216 let result;
217 while ((result = reasonRegexp.exec(text)) !== null) {
218 const contents = result[0];
219 const position = document.positionAt(result.index);
220 const locationOffset: SourceLocation = {
221 line: position.line + 1,
222 column: position.character + 1,
223 };
224 const source = new Source(contents, document.uri, locationOffset);
225 documents.push(new GraphQLDocument(source));
226 }
227
228 if (documents.length < 1) return null;
229
230 return documents;
231}
232
233function extractGraphQLDocumentsFromElixirStrings(
234 document: TextDocument,
235 tagName: string
236): GraphQLDocument[] | null {
237 const text = document.getText();
238 const documents: GraphQLDocument[] = [];
239
240 const regExp = new RegExp(
241 `\\b(${tagName}\\(\\s*r?("""))([\\s\\S]+?)\\2\\s*\\)`,
242 "gm"
243 );
244
245 let result;
246 while ((result = regExp.exec(text)) !== null) {
247 const contents = replacePlaceholdersWithWhiteSpace(result[3]);
248 const position = document.positionAt(result.index + result[1].length);
249 const locationOffset: SourceLocation = {
250 line: position.line + 1,
251 column: position.character + 1,
252 };
253 const source = new Source(contents, document.uri, locationOffset);
254 documents.push(new GraphQLDocument(source));
255 }
256
257 if (documents.length < 1) return null;
258
259 return documents;
260}
261
262function replacePlaceholdersWithWhiteSpace(content: string) {
263 return content.replace(/\$\{([\s\S]+?)\}/gm, (match) => {
264 return Array(match.length).join(" ");
265 });
266}
267
268function maybeCommentedOut(content: string) {
269 return (
270 (content.indexOf("/*") > -1 && content.indexOf("*/") > -1) ||
271 content.split("//").length > 1
272 );
273}