Skip to content

Commit 8a9fa96

Browse files
committed
fix(language-service): improve types, nullable member access
1 parent 614ff8b commit 8a9fa96

File tree

5 files changed

+108
-30
lines changed

5 files changed

+108
-30
lines changed

packages/graphql-language-service-server/src/GraphQLLanguageService.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,14 +379,12 @@ export class GraphQLLanguageService {
379379
}
380380

381381
output.push({
382-
// @ts-ignore
383382
name: tree.representativeName ?? 'Anonymous',
384383
kind: getKind(tree),
385384
location: {
386385
uri: filePath,
387386
range: {
388387
start: tree.startPosition,
389-
// @ts-ignore
390388
end: tree.endPosition,
391389
},
392390
},

packages/graphql-language-service-server/src/MessageProcessor.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ import {
6666
LoaderNoResultError,
6767
ProjectNotFoundError,
6868
} from 'graphql-config';
69-
import type { LoadConfigOptions, LocateCommand } from './types';
69+
import type {
70+
LoadConfigOptions,
71+
LocateCommand,
72+
VSCodeGraphQLConfigLoadSettings,
73+
VSCodeGraphQLSettings,
74+
} from './types';
7075
import {
7176
DEFAULT_SUPPORTED_EXTENSIONS,
7277
SupportedExtensionsEnum,
@@ -89,6 +94,13 @@ function toPosition(position: VscodePosition): IPosition {
8994
return new Position(position.line, position.character);
9095
}
9196

97+
interface MessageProcessorSettings extends VSCodeGraphQLSettings {
98+
load: VSCodeGraphQLConfigLoadSettings & {
99+
fileName?: string;
100+
[key: string]: unknown;
101+
};
102+
}
103+
92104
export class MessageProcessor {
93105
private _connection: Connection;
94106
private _graphQLCache!: GraphQLCache;
@@ -103,7 +115,7 @@ export class MessageProcessor {
103115
private _tmpDirBase: string;
104116
private _loadConfigOptions: LoadConfigOptions;
105117
private _rootPath: string = process.cwd();
106-
private _settings: any;
118+
private _settings: MessageProcessorSettings = { load: {} };
107119
private _providedConfig?: GraphQLConfig;
108120

109121
constructor({
@@ -210,7 +222,7 @@ export class MessageProcessor {
210222
// TODO: eventually we will instantiate an instance of this per workspace,
211223
// so rootDir should become that workspace's rootDir
212224
this._settings = { ...settings, ...vscodeSettings };
213-
const rootDir = this._settings?.load?.rootDir.length
225+
const rootDir = this._settings?.load?.rootDir?.length
214226
? this._settings?.load?.rootDir
215227
: this._rootPath;
216228
if (settings?.dotEnvPath) {

packages/graphql-language-service-server/src/types.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,45 @@ export interface ServerOptions {
115115
*/
116116
debug?: true;
117117
}
118+
119+
export interface VSCodeGraphQLSettings {
120+
/**
121+
* Enable debug logs and node debugger for client
122+
*/
123+
debug?: boolean | null;
124+
/**
125+
* Use a cached file output of your graphql-config schema result for definition lookups, symbols, outline, etc. Enabled by default when one or more schema entry is not a local file with SDL in it. Disable if you want to use SDL with a generated schema.
126+
*/
127+
cacheSchemaFileForLookup?: boolean;
128+
/**
129+
* Disables outlining and other expensive operations for files larger than this threshold (in bytes). Defaults to 1000000 (one million).
130+
*/
131+
largeFileThreshold?: number;
132+
/**
133+
* Fail the request on invalid certificate
134+
*/
135+
rejectUnauthorized?: boolean;
136+
/**
137+
* Schema cache ttl in milliseconds - the interval before requesting a fresh schema when caching the local schema file is enabled. Defaults to 30000 (30 seconds).
138+
*/
139+
schemaCacheTTL?: number;
140+
}
141+
142+
export interface VSCodeGraphQLConfigLoadSettings {
143+
/**
144+
* Base dir for graphql config loadConfig(), to look for config files or package.json
145+
*/
146+
rootDir?: string;
147+
/**
148+
* exact filePath for a `graphql-config` file `loadConfig()`
149+
*/
150+
filePath?: string;
151+
/**
152+
* optional <configName>.config.{js,ts,toml,yaml,json} & <configName>rc* instead of default `graphql`
153+
*/
154+
configName?: string;
155+
/**
156+
* legacy mode for graphql config v2 config
157+
*/
158+
legacy?: boolean;
159+
}

packages/graphql-language-service/src/interface/getOutline.ts

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import {
1919
Kind,
2020
parse,
2121
visit,
22+
ASTNode,
2223
FieldNode,
24+
ArgumentNode,
2325
InlineFragmentNode,
2426
DocumentNode,
2527
FragmentSpreadNode,
@@ -35,7 +37,9 @@ import {
3537
InputValueDefinitionNode,
3638
FieldDefinitionNode,
3739
EnumValueDefinitionNode,
40+
InputObjectTypeDefinitionNode,
3841
} from 'graphql';
42+
import type { ASTReducer } from 'graphql/language/visitor';
3943

4044
import { offsetToPosition } from '../utils';
4145

@@ -59,27 +63,47 @@ const OUTLINEABLE_KINDS = {
5963
FieldDefinition: true,
6064
};
6165

62-
export type OutlineableKinds = keyof typeof OUTLINEABLE_KINDS;
63-
64-
// type OutlineableNodes = FieldNode | OperationDefinitionNode | DocumentNode | SelectionSetNode | NameNode | FragmentDefinitionNode | FragmentSpreadNode |InlineFragmentNode | ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode
66+
type LiteralToEnum<Literal, Enum> = Enum extends never
67+
? never
68+
: { 0: Enum }[Enum extends Literal ? 0 : never];
69+
70+
export type OutlineableKinds = LiteralToEnum<
71+
keyof typeof OUTLINEABLE_KINDS,
72+
Kind
73+
>;
74+
75+
type OutlineableNode = Extract<ASTNode, { kind: OutlineableKinds }>;
76+
type AllKeys<T> = T extends unknown ? keyof T : never;
77+
type ExclusiveUnion<T, K extends PropertyKey = AllKeys<T>> = T extends never
78+
? never
79+
: T & Partial<Record<Exclude<K, keyof T>, never>>;
80+
81+
type OutlineTreeResultMeta = {
82+
representativeName?: string | NameNode;
83+
startPosition: IPosition;
84+
endPosition: IPosition;
85+
kind: OutlineableKinds;
86+
children:
87+
| SelectionSetNode
88+
| readonly ArgumentNode[]
89+
| readonly FieldDefinitionNode[]
90+
| readonly EnumValueDefinitionNode[]
91+
| readonly InputValueDefinitionNode[];
92+
};
6593

6694
type OutlineTreeResult =
67-
| {
68-
representativeName: string;
69-
startPosition: IPosition;
70-
endPosition: IPosition;
71-
children: SelectionSetNode[] | [];
72-
tokenizedText: TextToken[];
73-
}
95+
| (OutlineTreeResultMeta & { tokenizedText: TextToken[] })
7496
| string
7597
| readonly DefinitionNode[]
7698
| readonly SelectionNode[]
7799
| FieldNode[]
78100
| SelectionSetNode;
79101

80-
type OutlineTreeConverterType = Partial<{
81-
[key in OutlineableKinds]: (node: any) => OutlineTreeResult;
82-
}>;
102+
type OutlineTreeConverterType = {
103+
[key in OutlineableKinds]: (
104+
node: Extract<OutlineableNode, { kind: key }>,
105+
) => OutlineTreeResult;
106+
};
83107

84108
export function getOutline(documentText: string): Outline | null {
85109
let ast;
@@ -89,28 +113,30 @@ export function getOutline(documentText: string): Outline | null {
89113
return null;
90114
}
91115

92-
const visitorFns = outlineTreeConverter(documentText);
116+
type VisitorFns = Record<Kind, (node: ASTNode) => OutlineTreeResult>;
117+
const visitorFns = outlineTreeConverter(documentText) as VisitorFns;
93118
const outlineTrees = visit(ast, {
94-
leave(node) {
95-
if (visitorFns !== undefined && node.kind in visitorFns) {
96-
// @ts-ignore
119+
leave(node: ASTNode) {
120+
if (node.kind in visitorFns) {
97121
return visitorFns[node.kind](node);
98122
}
99123
return null;
100124
},
101-
}) as unknown as OutlineTree[];
125+
} as ASTReducer<OutlineTreeResult>) as OutlineTree[];
102126

103127
return { outlineTrees };
104128
}
105129

106130
function outlineTreeConverter(docText: string): OutlineTreeConverterType {
107-
// TODO: couldn't find a type that would work for all cases here,
108-
// however the inference is not broken by this at least
109-
const meta = (node: any) => {
131+
type MetaNode = Exclude<
132+
OutlineableNode,
133+
DocumentNode | SelectionSetNode | NameNode | InlineFragmentNode
134+
>;
135+
const meta = (node: ExclusiveUnion<MetaNode>): OutlineTreeResultMeta => {
110136
return {
111137
representativeName: node.name,
112-
startPosition: offsetToPosition(docText, node.loc.start),
113-
endPosition: offsetToPosition(docText, node.loc.end),
138+
startPosition: offsetToPosition(docText, node.loc!.start),
139+
endPosition: offsetToPosition(docText, node.loc!.end),
114140
kind: node.kind,
115141
children:
116142
node.selectionSet || node.fields || node.values || node.arguments || [],
@@ -176,7 +202,7 @@ function outlineTreeConverter(docText: string): OutlineTreeConverterType {
176202
],
177203
...meta(node),
178204
}),
179-
InputObjectTypeDefinition: (node: ObjectTypeDefinitionNode) => ({
205+
InputObjectTypeDefinition: (node: InputObjectTypeDefinitionNode) => ({
180206
tokenizedText: [
181207
buildToken('keyword', 'input'),
182208
buildToken('whitespace', ' '),

packages/graphql-language-service/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export type OutlineTree = {
217217
representativeName?: string;
218218
kind: string;
219219
startPosition: IPosition;
220-
endPosition?: IPosition;
220+
endPosition: IPosition;
221221
children: OutlineTree[];
222222
};
223223

0 commit comments

Comments
 (0)