@mo36924/graphql-plugin 1.4.34 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.js CHANGED
@@ -1,374 +1,297 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const graphqlSchema = require('@mo36924/graphql-schema');
6
- const graphql = require('graphql');
7
- const graphqlLanguageServiceInterface = require('graphql-language-service-interface');
8
- const graphqlLanguageServiceUtils = require('graphql-language-service-utils');
9
- const vscodeLanguageserverTypes = require('vscode-languageserver-types');
10
-
11
- const init = ({
12
- typescript: ts
13
- }) => {
14
- return {
15
- create(info) {
16
- const languageService = info.languageService;
17
- const config = info.config;
18
- const cwd = info.project.getCurrentDirectory();
19
- const modelPath = config.model && path.resolve(cwd, config.model);
20
- const schemaPath = config.schema && path.resolve(cwd, config.schema);
21
- const watchPath = modelPath || schemaPath;
22
- let schema = graphql.buildSchema("scalar Unknown");
23
-
24
- const addScalarUnknownType = schemaCode => schemaCode.includes("scalar Unknown") ? schemaCode : `${schemaCode}\nscalar Unknown`;
25
-
26
- const changeModel = () => {
27
- schema = graphql.buildSchema(addScalarUnknownType(graphqlSchema.printSchemaModel(fs.readFileSync(modelPath, "utf8"))));
28
- };
29
-
30
- const changeSchema = () => {
31
- schema = graphql.buildSchema(addScalarUnknownType(fs.readFileSync(schemaPath, "utf8")));
32
- };
33
-
34
- const update = modelPath ? changeModel : changeSchema;
35
-
36
- const listener = () => {
37
- try {
38
- update();
39
- } catch {}
40
- };
41
-
42
- try {
43
- update();
44
- fs.watch(watchPath, listener);
45
- } catch {
46
- fs.watchFile(watchPath, () => {
47
- try {
48
- update();
49
- fs.watch(watchPath, listener);
50
- fs.unwatchFile(watchPath);
51
- } catch {}
52
- });
53
- }
54
-
55
- const getSourceFile = fileName => languageService.getProgram()?.getSourceFile(fileName);
56
-
57
- const isGraphqlTag = tag => {
58
- switch (tag) {
59
- case "query":
60
- case "mutation":
61
- case "subscription":
62
- return true;
63
-
64
- default:
65
- return false;
66
- }
67
- };
68
-
69
- const getDiagnosticCategory = severity => {
70
- switch (severity) {
71
- case vscodeLanguageserverTypes.DiagnosticSeverity.Error:
72
- return ts.DiagnosticCategory.Error;
73
-
74
- case vscodeLanguageserverTypes.DiagnosticSeverity.Warning:
75
- return ts.DiagnosticCategory.Warning;
76
-
77
- case vscodeLanguageserverTypes.DiagnosticSeverity.Information:
78
- return ts.DiagnosticCategory.Message;
79
-
80
- case vscodeLanguageserverTypes.DiagnosticSeverity.Hint:
81
- return ts.DiagnosticCategory.Suggestion;
82
-
83
- default:
84
- return ts.DiagnosticCategory.Error;
85
- }
86
- };
87
-
88
- const hover = (sourceFile, position) => {
89
- const tag = ts.forEachChild(sourceFile, function visitor(node) {
90
- if (position < node.pos) {
91
- return true;
92
- }
93
-
94
- if (position >= node.end) {
95
- return;
96
- }
97
-
98
- if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && isGraphqlTag(node.tag.getText())) {
99
- const template = node.template;
100
-
101
- if (ts.isNoSubstitutionTemplateLiteral(template)) {
102
- if (position >= template.getStart() + 1 && position < template.getEnd() - 1) {
103
- return node;
104
- }
105
- } else {
106
- const head = template.head;
107
-
108
- if (position >= head.getStart() + 1 && position < head.getEnd() - 2) {
109
- return node;
110
- }
111
-
112
- for (const {
113
- literal
114
- } of template.templateSpans) {
115
- if (position >= literal.getStart() + 1 && position < literal.getEnd() - (ts.isTemplateMiddle(literal) ? 2 : 1)) {
116
- return node;
1
+ import { watch, watchFile, unwatchFile, readFileSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { printSchemaModel } from '@mo36924/graphql-schema';
4
+ import { buildSchema, GraphQLError, parse, validate } from 'graphql';
5
+ import { getTokenAtPosition, getHoverInformation, getAutocompleteSuggestions, getDiagnostics } from 'graphql-language-service-interface';
6
+ import { Position } from 'graphql-language-service-utils';
7
+ import { CompletionItemKind, DiagnosticSeverity } from 'vscode-languageserver-types';
8
+
9
+ const init = ({ typescript: ts }) => {
10
+ return {
11
+ create(info) {
12
+ const languageService = info.languageService;
13
+ const config = info.config;
14
+ const cwd = info.project.getCurrentDirectory();
15
+ const modelPath = config.model && resolve(cwd, config.model);
16
+ const schemaPath = config.schema && resolve(cwd, config.schema);
17
+ const watchPath = modelPath || schemaPath;
18
+ let schema = buildSchema("scalar Unknown");
19
+ const addScalarUnknownType = (schemaCode) => schemaCode.includes("scalar Unknown") ? schemaCode : `${schemaCode}\nscalar Unknown`;
20
+ const changeModel = () => {
21
+ schema = buildSchema(addScalarUnknownType(printSchemaModel(readFileSync(modelPath, "utf8"))));
22
+ };
23
+ const changeSchema = () => {
24
+ schema = buildSchema(addScalarUnknownType(readFileSync(schemaPath, "utf8")));
25
+ };
26
+ const update = modelPath ? changeModel : changeSchema;
27
+ const listener = () => {
28
+ try {
29
+ update();
117
30
  }
118
- }
119
- }
120
- }
121
-
122
- return ts.forEachChild(node, visitor);
123
- });
124
-
125
- if (tag === true) {
126
- return;
127
- }
128
-
129
- return tag;
130
- };
131
-
132
- const fix = node => {
133
- const template = node.template;
134
- let query = "";
135
- let variables = "";
136
-
137
- if (ts.isNoSubstitutionTemplateLiteral(template)) {
138
- // 2 ``
139
- const templateWidth = template.getWidth() - 2;
140
- query = template.text.padStart(templateWidth);
141
- } else {
142
- const head = template.head;
143
- const templateSpans = template.templateSpans; // 3 `...${
144
-
145
- const templateWidth = head.getWidth() - 3;
146
- query = head.text.padStart(templateWidth);
147
- templateSpans.forEach((span, i) => {
148
- const spanWidth = span.getFullWidth();
149
- const literal = span.literal;
150
- const literalWidth = literal.getWidth();
151
- const expressionWidth = spanWidth - literalWidth;
152
- const variableName = `$_${i}`;
153
- const variable = variableName.padStart(expressionWidth + 2).padEnd(expressionWidth + 3);
154
- const templateWidth = literalWidth - (ts.isTemplateTail(literal) ? 2 : 3);
155
- const template = literal.text.padStart(templateWidth);
156
- query += variable + template;
157
- variables += variableName + ":Unknown";
158
- });
159
- }
160
-
161
- const tag = node.tag.getText();
162
- let offset = template.getStart() + 1;
163
- query = query.replace(/\n|\r/g, " ");
164
-
165
- if (variables) {
166
- query = `${tag}(${variables}){${query}}`;
167
- offset -= tag.length + variables.length + 3;
168
- } else if (tag === "query") {
169
- query = `{${query}}`;
170
- offset -= 1;
171
- } else {
172
- query = `${tag}{${query}}`;
173
- offset -= tag.length + 1;
174
- }
175
-
176
- const documentNode = graphql.parse(query);
177
- const errors = graphql.validate(schema, documentNode);
178
-
179
- for (const error of errors) {
180
- const match = error.message.match(/^Variable ".*?" of type "Unknown" used in position expecting type "(.*?)"\.$/);
181
-
182
- if (match) {
183
- query = query.replace("Unknown", match[1]);
184
- offset += 7 - match[1].length;
185
- }
186
- }
187
-
188
- return {
189
- query,
190
- offset
191
- };
192
- };
193
-
194
- const proxy = Object.create(null);
195
-
196
- for (const [key, value] of Object.entries(languageService)) {
197
- proxy[key] = value.bind(languageService);
198
- }
199
-
200
- proxy.getQuickInfoAtPosition = (fileName, position) => {
201
- const sourceFile = getSourceFile(fileName);
202
-
203
- if (!sourceFile) {
204
- return undefined;
205
- }
206
-
207
- const tag = hover(sourceFile, position);
208
-
209
- if (!tag) {
210
- return languageService.getQuickInfoAtPosition(fileName, position);
211
- }
212
-
213
- let result;
214
-
215
- try {
216
- result = fix(tag);
217
- } catch {
218
- return languageService.getQuickInfoAtPosition(fileName, position);
219
- }
220
-
221
- const {
222
- query,
223
- offset
224
- } = result;
225
- const cursor = new graphqlLanguageServiceUtils.Position(0, position - offset + 1);
226
- const token = graphqlLanguageServiceInterface.getTokenAtPosition(query, cursor);
227
- const marked = graphqlLanguageServiceInterface.getHoverInformation(schema, query, cursor, token);
228
-
229
- if (marked === "" || typeof marked !== "string") {
230
- return;
231
- }
232
-
233
- return {
234
- kind: ts.ScriptElementKind.string,
235
- textSpan: {
236
- start: offset + token.start,
237
- length: token.end - token.start
238
- },
239
- kindModifiers: "",
240
- displayParts: [{
241
- text: marked,
242
- kind: ""
243
- }]
244
- };
245
- };
246
-
247
- proxy.getCompletionsAtPosition = (fileName, position, options) => {
248
- const sourceFile = getSourceFile(fileName);
249
-
250
- if (!sourceFile) {
251
- return undefined;
252
- }
253
-
254
- const tag = hover(sourceFile, position);
255
-
256
- if (!tag) {
257
- return languageService.getCompletionsAtPosition(fileName, position, options);
258
- }
259
-
260
- let result;
261
-
262
- try {
263
- result = fix(tag);
264
- } catch {
265
- return languageService.getCompletionsAtPosition(fileName, position, options);
266
- }
267
-
268
- const {
269
- query,
270
- offset
271
- } = result;
272
- const cursor = new graphqlLanguageServiceUtils.Position(0, position - offset);
273
- const token = graphqlLanguageServiceInterface.getTokenAtPosition(query, cursor);
274
- const items = graphqlLanguageServiceInterface.getAutocompleteSuggestions(schema, query, cursor, token);
275
-
276
- if (!items.length) {
277
- return;
278
- }
279
-
280
- return {
281
- isGlobalCompletion: false,
282
- isMemberCompletion: false,
283
- isNewIdentifierLocation: false,
284
- entries: items.map(item => {
285
- let kind;
286
-
287
- switch (item.kind) {
288
- case vscodeLanguageserverTypes.CompletionItemKind.Function:
289
- case vscodeLanguageserverTypes.CompletionItemKind.Constructor:
290
- kind = ts.ScriptElementKind.functionElement;
291
- break;
292
-
293
- case vscodeLanguageserverTypes.CompletionItemKind.Field:
294
- case vscodeLanguageserverTypes.CompletionItemKind.Variable:
295
- kind = ts.ScriptElementKind.memberVariableElement;
296
- break;
297
-
298
- default:
299
- kind = ts.ScriptElementKind.unknown;
300
- break;
301
- }
302
-
303
- return {
304
- name: item.label,
305
- kindModifiers: "",
306
- kind,
307
- sortText: ""
31
+ catch { }
308
32
  };
309
- })
310
- };
311
- };
312
-
313
- proxy.getSemanticDiagnostics = fileName => {
314
- const diagnostics = languageService.getSemanticDiagnostics(fileName);
315
- const sourceFile = getSourceFile(fileName);
316
-
317
- if (!sourceFile) {
318
- return diagnostics;
319
- }
320
-
321
- ts.forEachChild(sourceFile, function visitor(node) {
322
- if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && isGraphqlTag(node.tag.getText())) {
323
33
  try {
324
- const {
325
- query,
326
- offset
327
- } = fix(node);
328
-
329
- const _diagnostics = graphqlLanguageServiceInterface.getDiagnostics(query, schema);
330
-
331
- for (const {
332
- range: {
333
- start,
334
- end
335
- },
336
- severity,
337
- message
338
- } of _diagnostics) {
339
- diagnostics.push({
340
- category: getDiagnosticCategory(severity),
341
- code: 9999,
342
- messageText: message,
343
- file: sourceFile,
344
- start: start.character + offset,
345
- length: end.character - start.character
34
+ update();
35
+ watch(watchPath, listener);
36
+ }
37
+ catch {
38
+ watchFile(watchPath, () => {
39
+ try {
40
+ update();
41
+ watch(watchPath, listener);
42
+ unwatchFile(watchPath);
43
+ }
44
+ catch { }
346
45
  });
347
- }
348
- } catch (error) {
349
- if (error instanceof graphql.GraphQLError) {
350
- diagnostics.push({
351
- category: ts.DiagnosticCategory.Error,
352
- code: 9999,
353
- messageText: error.message,
354
- file: sourceFile,
355
- start: node.template.getStart() + 1,
356
- length: node.template.getWidth() - 2
46
+ }
47
+ const getSourceFile = (fileName) => languageService.getProgram()?.getSourceFile(fileName);
48
+ const isGraphqlTag = (tag) => {
49
+ switch (tag) {
50
+ case "query":
51
+ case "mutation":
52
+ case "subscription":
53
+ return true;
54
+ default:
55
+ return false;
56
+ }
57
+ };
58
+ const getDiagnosticCategory = (severity) => {
59
+ switch (severity) {
60
+ case DiagnosticSeverity.Error:
61
+ return ts.DiagnosticCategory.Error;
62
+ case DiagnosticSeverity.Warning:
63
+ return ts.DiagnosticCategory.Warning;
64
+ case DiagnosticSeverity.Information:
65
+ return ts.DiagnosticCategory.Message;
66
+ case DiagnosticSeverity.Hint:
67
+ return ts.DiagnosticCategory.Suggestion;
68
+ default:
69
+ return ts.DiagnosticCategory.Error;
70
+ }
71
+ };
72
+ const hover = (sourceFile, position) => {
73
+ const tag = ts.forEachChild(sourceFile, function visitor(node) {
74
+ if (position < node.pos) {
75
+ return true;
76
+ }
77
+ if (position >= node.end) {
78
+ return;
79
+ }
80
+ if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && isGraphqlTag(node.tag.getText())) {
81
+ const template = node.template;
82
+ if (ts.isNoSubstitutionTemplateLiteral(template)) {
83
+ if (position >= template.getStart() + 1 && position < template.getEnd() - 1) {
84
+ return node;
85
+ }
86
+ }
87
+ else {
88
+ const head = template.head;
89
+ if (position >= head.getStart() + 1 && position < head.getEnd() - 2) {
90
+ return node;
91
+ }
92
+ for (const { literal } of template.templateSpans) {
93
+ if (position >= literal.getStart() + 1 &&
94
+ position < literal.getEnd() - (ts.isTemplateMiddle(literal) ? 2 : 1)) {
95
+ return node;
96
+ }
97
+ }
98
+ }
99
+ }
100
+ return ts.forEachChild(node, visitor);
357
101
  });
358
- }
102
+ if (tag === true) {
103
+ return;
104
+ }
105
+ return tag;
106
+ };
107
+ const fix = (node) => {
108
+ const template = node.template;
109
+ let query = "";
110
+ let variables = "";
111
+ if (ts.isNoSubstitutionTemplateLiteral(template)) {
112
+ // 2 ``
113
+ const templateWidth = template.getWidth() - 2;
114
+ query = template.text.padStart(templateWidth);
115
+ }
116
+ else {
117
+ const head = template.head;
118
+ const templateSpans = template.templateSpans;
119
+ // 3 `...${
120
+ const templateWidth = head.getWidth() - 3;
121
+ query = head.text.padStart(templateWidth);
122
+ templateSpans.forEach((span, i) => {
123
+ const spanWidth = span.getFullWidth();
124
+ const literal = span.literal;
125
+ const literalWidth = literal.getWidth();
126
+ const expressionWidth = spanWidth - literalWidth;
127
+ const variableName = `$_${i}`;
128
+ const variable = variableName.padStart(expressionWidth + 2).padEnd(expressionWidth + 3);
129
+ const templateWidth = literalWidth - (ts.isTemplateTail(literal) ? 2 : 3);
130
+ const template = literal.text.padStart(templateWidth);
131
+ query += variable + template;
132
+ variables += variableName + ":Unknown";
133
+ });
134
+ }
135
+ const tag = node.tag.getText();
136
+ let offset = template.getStart() + 1;
137
+ query = query.replace(/\n|\r/g, " ");
138
+ if (variables) {
139
+ query = `${tag}(${variables}){${query}}`;
140
+ offset -= tag.length + variables.length + 3;
141
+ }
142
+ else if (tag === "query") {
143
+ query = `{${query}}`;
144
+ offset -= 1;
145
+ }
146
+ else {
147
+ query = `${tag}{${query}}`;
148
+ offset -= tag.length + 1;
149
+ }
150
+ const documentNode = parse(query);
151
+ const errors = validate(schema, documentNode);
152
+ for (const error of errors) {
153
+ const match = error.message.match(/^Variable ".*?" of type "Unknown" used in position expecting type "(.*?)"\.$/);
154
+ if (match) {
155
+ query = query.replace("Unknown", match[1]);
156
+ offset += 7 - match[1].length;
157
+ }
158
+ }
159
+ return {
160
+ query,
161
+ offset,
162
+ };
163
+ };
164
+ const proxy = Object.create(null);
165
+ for (const [key, value] of Object.entries(languageService)) {
166
+ proxy[key] = value.bind(languageService);
359
167
  }
360
- }
361
-
362
- ts.forEachChild(node, visitor);
363
- });
364
- return diagnostics;
365
- };
366
-
367
- return proxy;
368
- }
369
-
370
- };
168
+ proxy.getQuickInfoAtPosition = (fileName, position) => {
169
+ const sourceFile = getSourceFile(fileName);
170
+ if (!sourceFile) {
171
+ return undefined;
172
+ }
173
+ const tag = hover(sourceFile, position);
174
+ if (!tag) {
175
+ return languageService.getQuickInfoAtPosition(fileName, position);
176
+ }
177
+ let result;
178
+ try {
179
+ result = fix(tag);
180
+ }
181
+ catch {
182
+ return languageService.getQuickInfoAtPosition(fileName, position);
183
+ }
184
+ const { query, offset } = result;
185
+ const cursor = new Position(0, position - offset + 1);
186
+ const token = getTokenAtPosition(query, cursor);
187
+ const marked = getHoverInformation(schema, query, cursor, token);
188
+ if (marked === "" || typeof marked !== "string") {
189
+ return;
190
+ }
191
+ return {
192
+ kind: ts.ScriptElementKind.string,
193
+ textSpan: {
194
+ start: offset + token.start,
195
+ length: token.end - token.start,
196
+ },
197
+ kindModifiers: "",
198
+ displayParts: [{ text: marked, kind: "" }],
199
+ };
200
+ };
201
+ proxy.getCompletionsAtPosition = (fileName, position, options) => {
202
+ const sourceFile = getSourceFile(fileName);
203
+ if (!sourceFile) {
204
+ return undefined;
205
+ }
206
+ const tag = hover(sourceFile, position);
207
+ if (!tag) {
208
+ return languageService.getCompletionsAtPosition(fileName, position, options);
209
+ }
210
+ let result;
211
+ try {
212
+ result = fix(tag);
213
+ }
214
+ catch {
215
+ return languageService.getCompletionsAtPosition(fileName, position, options);
216
+ }
217
+ const { query, offset } = result;
218
+ const cursor = new Position(0, position - offset);
219
+ const token = getTokenAtPosition(query, cursor);
220
+ const items = getAutocompleteSuggestions(schema, query, cursor, token);
221
+ if (!items.length) {
222
+ return;
223
+ }
224
+ return {
225
+ isGlobalCompletion: false,
226
+ isMemberCompletion: false,
227
+ isNewIdentifierLocation: false,
228
+ entries: items.map((item) => {
229
+ let kind;
230
+ switch (item.kind) {
231
+ case CompletionItemKind.Function:
232
+ case CompletionItemKind.Constructor:
233
+ kind = ts.ScriptElementKind.functionElement;
234
+ break;
235
+ case CompletionItemKind.Field:
236
+ case CompletionItemKind.Variable:
237
+ kind = ts.ScriptElementKind.memberVariableElement;
238
+ break;
239
+ default:
240
+ kind = ts.ScriptElementKind.unknown;
241
+ break;
242
+ }
243
+ return {
244
+ name: item.label,
245
+ kindModifiers: "",
246
+ kind,
247
+ sortText: "",
248
+ };
249
+ }),
250
+ };
251
+ };
252
+ proxy.getSemanticDiagnostics = (fileName) => {
253
+ const diagnostics = languageService.getSemanticDiagnostics(fileName);
254
+ const sourceFile = getSourceFile(fileName);
255
+ if (!sourceFile) {
256
+ return diagnostics;
257
+ }
258
+ ts.forEachChild(sourceFile, function visitor(node) {
259
+ if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && isGraphqlTag(node.tag.getText())) {
260
+ try {
261
+ const { query, offset } = fix(node);
262
+ const _diagnostics = getDiagnostics(query, schema);
263
+ for (const { range: { start, end }, severity, message, } of _diagnostics) {
264
+ diagnostics.push({
265
+ category: getDiagnosticCategory(severity),
266
+ code: 9999,
267
+ messageText: message,
268
+ file: sourceFile,
269
+ start: start.character + offset,
270
+ length: end.character - start.character,
271
+ });
272
+ }
273
+ }
274
+ catch (error) {
275
+ if (error instanceof GraphQLError) {
276
+ diagnostics.push({
277
+ category: ts.DiagnosticCategory.Error,
278
+ code: 9999,
279
+ messageText: error.message,
280
+ file: sourceFile,
281
+ start: node.template.getStart() + 1,
282
+ length: node.template.getWidth() - 2,
283
+ });
284
+ }
285
+ }
286
+ }
287
+ ts.forEachChild(node, visitor);
288
+ });
289
+ return diagnostics;
290
+ };
291
+ return proxy;
292
+ },
293
+ };
371
294
  };
372
295
 
373
- module.exports = init;
296
+ export { init as default };
374
297
  //# sourceMappingURL=index.js.map