···
isTaggedTemplateExpression,
15
+
} from 'graphql-language-service';
17
+
FragmentDefinitionNode,
19
+
GraphQLCompositeType,
23
+
GraphQLInputFieldMap,
24
+
GraphQLInterfaceType,
export function isFileDirty(fileName: string, source: ts.SourceFile) {
const contents = fs.readFileSync(fileName, 'utf-8');
···
83
+
* This part is vendored from https://github.com/graphql/graphiql/blob/main/packages/graphql-language-service/src/interface/autocompleteUtils.ts#L97
85
+
export type CompletionItemBase = {
87
+
isDeprecated?: boolean;
90
+
// Create the expected hint response given a possible list and a token
91
+
export function hintList<T extends CompletionItemBase>(
92
+
token: ContextTokenUnion,
95
+
return filterAndSortList(list, normalizeText(token.string));
98
+
// Given a list of hint entries and currently typed text, sort and filter to
99
+
// provide a concise list.
100
+
function filterAndSortList<T extends CompletionItemBase>(
105
+
return filterNonEmpty<T>(list, entry => !entry.isDeprecated);
108
+
const byProximity = list.map(entry => ({
109
+
proximity: getProximity(normalizeText(entry.label), text),
113
+
return filterNonEmpty(
114
+
filterNonEmpty(byProximity, pair => pair.proximity <= 2),
115
+
pair => !pair.entry.isDeprecated
119
+
(a.entry.isDeprecated ? 1 : 0) - (b.entry.isDeprecated ? 1 : 0) ||
120
+
a.proximity - b.proximity ||
121
+
a.entry.label.length - b.entry.label.length
123
+
.map(pair => pair.entry);
126
+
// Filters the array by the predicate, unless it results in an empty array,
127
+
// in which case return the original array.
128
+
function filterNonEmpty<T>(
130
+
predicate: (entry: T) => boolean
132
+
const filtered = array.filter(predicate);
133
+
return filtered.length === 0 ? array : filtered;
136
+
function normalizeText(text: string): string {
137
+
return text.toLowerCase().replace(/\W/g, '');
140
+
// Determine a numeric proximity for a suggestion based on current text.
141
+
function getProximity(suggestion: string, text: string): number {
142
+
// start with lexical distance
143
+
let proximity = lexicalDistance(text, suggestion);
144
+
if (suggestion.length > text.length) {
145
+
// do not penalize long suggestions.
146
+
proximity -= suggestion.length - text.length - 1;
147
+
// penalize suggestions not starting with this phrase
148
+
proximity += suggestion.indexOf(text) === 0 ? 0 : 0.5;
154
+
* Computes the lexical distance between strings A and B.
156
+
* The "distance" between two strings is given by counting the minimum number
157
+
* of edits needed to transform string A into string B. An edit can be an
158
+
* insertion, deletion, or substitution of a single character, or a swap of two
159
+
* adjacent characters.
161
+
* This distance can be useful for detecting typos in input or sorting
163
+
* @param {string} a
164
+
* @param {string} b
165
+
* @return {int} distance in number of edits
167
+
function lexicalDistance(a: string, b: string): number {
171
+
const aLength = a.length;
172
+
const bLength = b.length;
174
+
for (i = 0; i <= aLength; i++) {
178
+
for (j = 1; j <= bLength; j++) {
182
+
for (i = 1; i <= aLength; i++) {
183
+
for (j = 1; j <= bLength; j++) {
184
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
186
+
d[i][j] = Math.min(
189
+
d[i - 1][j - 1] + cost
192
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
193
+
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
198
+
return d[aLength][bLength];
201
+
export type AllTypeInfo = {
202
+
type: Maybe<GraphQLType>;
203
+
parentType: Maybe<GraphQLType>;
204
+
inputType: Maybe<GraphQLType>;
205
+
directiveDef: Maybe<GraphQLDirective>;
206
+
fieldDef: Maybe<GraphQLField<any, any>>;
207
+
enumValue: Maybe<GraphQLEnumValue>;
208
+
argDef: Maybe<GraphQLArgument>;
209
+
argDefs: Maybe<GraphQLArgument[]>;
210
+
objectFieldDefs: Maybe<GraphQLInputFieldMap>;
211
+
interfaceDef: Maybe<GraphQLInterfaceType>;
212
+
objectTypeDef: Maybe<GraphQLObjectType>;
216
+
* This is vendored from https://github.com/graphql/graphiql/blob/main/packages/graphql-language-service/src/interface/getAutocompleteSuggestions.ts#L779
218
+
export function getSuggestionsForFragmentSpread(
219
+
token: ContextToken,
220
+
typeInfo: AllTypeInfo,
221
+
schema: GraphQLSchema,
223
+
fragments: FragmentDefinitionNode[]
224
+
): Array<CompletionItem> {
229
+
const typeMap = schema.getTypeMap();
230
+
const defState = getDefinitionState(token.state);
232
+
// Filter down to only the fragments which may exist here.
233
+
const relevantFrags = fragments.filter(
235
+
// Only include fragments with known types.
236
+
typeMap[frag.typeCondition.name.value] &&
237
+
// Only include fragments which are not cyclic.
240
+
defState.kind === RuleKinds.FRAGMENT_DEFINITION &&
241
+
defState.name === frag.name.value
243
+
// Only include fragments which could possibly be spread here.
244
+
isCompositeType(typeInfo.parentType) &&
245
+
isCompositeType(typeMap[frag.typeCondition.name.value]) &&
248
+
typeInfo.parentType,
249
+
typeMap[frag.typeCondition.name.value] as GraphQLCompositeType
255
+
relevantFrags.map(frag => ({
256
+
label: frag.name.value,
257
+
detail: String(typeMap[frag.typeCondition.name.value]),
258
+
documentation: `fragment ${frag.name.value} on ${frag.typeCondition.name.value}`,
259
+
kind: CompletionItemKind.Field,
260
+
type: typeMap[frag.typeCondition.name.value],