Skip to content

Commit f221093

Browse files
jasonpaulosessaji
authored andcommitted
Add client schema support to vscode extension (apollographql#1433)
* Augment the client schema with metadata about which fields/objects are client-only. * Show information about client-only fields on hover. * Add auto complete support for injecting client directives when appropriate. * Add a validation rule to detect missing client directives. * Add a code action to suggest the addition of a clienet directive if it is missing. Also add the ability in general for validation rules to do this. * Add go to definition support for custom client-only directives. * Add information on hover for directives. * Add auto complete support for directives. * Forbid unknown directives. * Add built-in apollo directives as a fallback
1 parent 4bd97f9 commit f221093

11 files changed

Lines changed: 593 additions & 57 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
- `apollo-language-server`
2222
- Add debugging util classes for better error/warning handling [#1429](https://github.com/apollographql/apollo-tooling/pull/1429)
2323
- Add error for duplicate client operation names [#1466](https://github.com/apollographql/apollo-tooling/pull/1466)
24+
- Add client schema support through autocomplete, hover information, validation rules, and code actions. [#1433](https://github.com/apollographql/apollo-tooling/pull/1433)
2425
- `apollo-tools`
2526
- <First `apollo-tools` related entry goes here>
2627
- `vscode-apollo`
28+
- Improve the syntax highlighting of directives and their definitions. [#1433](https://github.com/apollographql/apollo-tooling/pull/1433)
2729
- Add debugging util class for better logging in vs code [#1429](https://github.com/apollographql/apollo-tooling/pull/1429)
2830

2931
## `apollo-language-server@1.14.3`

packages/apollo-language-server/src/diagnostics.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function diagnosticsFromError(
6262
error: GraphQLError,
6363
severity: DiagnosticSeverity,
6464
type: string
65-
): Diagnostic[] {
65+
): GraphQLDiagnostic[] {
6666
if (!error.nodes) {
6767
return [];
6868
}
@@ -72,11 +72,25 @@ export function diagnosticsFromError(
7272
source: `GraphQL: ${type}`,
7373
message: error.message,
7474
severity,
75-
range: rangeForASTNode(highlightNodeForNode(node) || node)
75+
range: rangeForASTNode(highlightNodeForNode(node) || node),
76+
error
7677
};
7778
});
7879
}
7980

81+
export interface GraphQLDiagnostic extends Diagnostic {
82+
/**
83+
* The GraphQLError that produced this Diagnostic
84+
*/
85+
error: GraphQLError;
86+
}
87+
88+
export namespace GraphQLDiagnostic {
89+
export function is(diagnostic: Diagnostic): diagnostic is GraphQLDiagnostic {
90+
return "error" in diagnostic;
91+
}
92+
}
93+
8094
export class DiagnosticSet {
8195
private diagnosticsByFile = new Map<DocumentUri, Diagnostic[]>();
8296

packages/apollo-language-server/src/errors/validation.ts

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
specifiedRules,
33
NoUnusedFragmentsRule,
4-
KnownDirectivesRule,
54
GraphQLError,
65
FieldNode,
76
ValidationContext,
@@ -12,17 +11,30 @@ import {
1211
FragmentDefinitionNode,
1312
visit,
1413
visitWithTypeInfo,
15-
visitInParallel
14+
visitInParallel,
15+
getLocation
1616
} from "graphql";
1717

18+
import { TextEdit } from "vscode-languageserver";
19+
1820
import { ToolError, logError } from "./logger";
1921
import { ValidationRule } from "graphql/validation/ValidationContext";
22+
import {
23+
positionFromSourceLocation,
24+
isFieldResolvedLocally
25+
} from "../utilities/source";
26+
27+
export interface CodeActionInfo {
28+
message: string;
29+
edits: TextEdit[];
30+
}
2031

21-
const specifiedRulesToBeRemoved = [NoUnusedFragmentsRule, KnownDirectivesRule];
32+
const specifiedRulesToBeRemoved = [NoUnusedFragmentsRule];
2233

2334
export const defaultValidationRules: ValidationRule[] = [
2435
NoAnonymousQueries,
2536
NoTypenameAlias,
37+
NoMissingClientDirectives,
2638
...specifiedRules.filter(rule => !specifiedRulesToBeRemoved.includes(rule))
2739
];
2840

@@ -93,3 +105,66 @@ export function NoTypenameAlias(context: ValidationContext) {
93105
}
94106
};
95107
}
108+
109+
export function NoMissingClientDirectives(context: ValidationContext) {
110+
const root = context.getDocument();
111+
return {
112+
Field(node: FieldNode) {
113+
const parentType = context.getParentType();
114+
const fieldDef = context.getFieldDef();
115+
116+
if (!parentType || !fieldDef) return;
117+
118+
const isClientType =
119+
parentType.clientSchema &&
120+
parentType.clientSchema.localFields &&
121+
parentType.clientSchema.localFields.includes(fieldDef.name);
122+
123+
const isResolvedLocally = isFieldResolvedLocally(node, root);
124+
125+
if (isClientType && !isResolvedLocally) {
126+
let extensions: { [key: string]: any } | null = null;
127+
const nameLoc = node.name.loc;
128+
if (nameLoc) {
129+
let { source, end: locToInsertDirective } = nameLoc;
130+
if (node.arguments && node.arguments.length !== 0) {
131+
// must insert directive after field arguments
132+
const endOfArgs = source.body.indexOf(")", locToInsertDirective);
133+
locToInsertDirective = endOfArgs + 1;
134+
}
135+
const codeAction: CodeActionInfo = {
136+
message: `Add @client directive to "${node.name.value}"`,
137+
edits: [
138+
TextEdit.insert(
139+
positionFromSourceLocation(
140+
source,
141+
getLocation(source, locToInsertDirective)
142+
),
143+
" @client"
144+
)
145+
]
146+
};
147+
extensions = { codeAction };
148+
}
149+
150+
context.reportError(
151+
new GraphQLError(
152+
`Local field "${node.name.value}" must have a @client directive`,
153+
[node],
154+
null,
155+
null,
156+
null,
157+
null,
158+
extensions
159+
)
160+
);
161+
}
162+
163+
if (isClientType) {
164+
return false;
165+
}
166+
167+
return;
168+
}
169+
};
170+
}

0 commit comments

Comments
 (0)