Skip to content

Commit

Permalink
first passthrough for multi schema support
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Apr 25, 2024
1 parent c4630f6 commit 9dd4f5a
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 19 deletions.
43 changes: 36 additions & 7 deletions packages/graphqlsp/src/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,45 @@ export function unrollTadaFragments(
return wip;
}

export const getSchemaName = (
node: ts.CallExpression,
typeChecker?: ts.TypeChecker
): string => {
if (!typeChecker) return 'defeault';

const type = typeChecker.getTypeAtLocation(node.expression);
if (type) {
const brandTypeSymbol = type.getProperty('__brand');
if (brandTypeSymbol) {
const brand = typeChecker.getTypeOfSymbol(brandTypeSymbol);
if (brand.isStringLiteral()) {
return brand.value;
}
}
}

return 'default';
};

export function findAllCallExpressions(
sourceFile: ts.SourceFile,
info: ts.server.PluginCreateInfo,
shouldSearchFragments: boolean = true
): {
nodes: Array<ts.NoSubstitutionTemplateLiteral>;
nodes: Array<{ node: ts.NoSubstitutionTemplateLiteral; schema: string }>;
fragments: Array<FragmentDefinitionNode>;
} {
const result: Array<ts.NoSubstitutionTemplateLiteral> = [];
const typeChecker = info.languageService.getProgram()?.getTypeChecker();
const result: Array<{
node: ts.NoSubstitutionTemplateLiteral;
schema: string;
}> = [];
let fragments: Array<FragmentDefinitionNode> = [];
let hasTriedToFindFragments = shouldSearchFragments ? false : true;
function find(node: ts.Node) {
if (ts.isCallExpression(node) && templates.has(node.expression.getText())) {
const name = getSchemaName(node, typeChecker);

const [arg, arg2] = node.arguments;

if (!hasTriedToFindFragments && !arg2) {
Expand All @@ -160,7 +186,7 @@ export function findAllCallExpressions(
}

if (arg && ts.isNoSubstitutionTemplateLiteral(arg)) {
result.push(arg);
result.push({ node: arg, schema: name });
}
return;
} else {
Expand All @@ -172,11 +198,14 @@ export function findAllCallExpressions(
}

export function findAllPersistedCallExpressions(
sourceFile: ts.SourceFile
): Array<ts.CallExpression> {
const result: Array<ts.CallExpression> = [];
sourceFile: ts.SourceFile,
info: ts.server.PluginCreateInfo
): Array<{ node: ts.CallExpression; schema: string }> {
const result: Array<{ node: ts.CallExpression; schema: string }> = [];
const typeChecker = info.languageService.getProgram()?.getTypeChecker();
function find(node: ts.Node) {
if (ts.isCallExpression(node)) {
const name = getSchemaName(node, typeChecker);
// This expression ideally for us looks like <template>.persisted
const expression = node.expression.getText();
const parts = expression.split('.');
Expand All @@ -185,7 +214,7 @@ export function findAllPersistedCallExpressions(
const [template, method] = parts;
if (!templates.has(template) || method !== 'persisted') return;

result.push(node);
result.push({ node, schema: name });
} else {
ts.forEachChild(node, find);
}
Expand Down
23 changes: 20 additions & 3 deletions packages/graphqlsp/src/autoComplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
bubbleUpTemplate,
findNode,
getAllFragments,
getSchemaName,
getSource,
} from './ast';
import { Cursor } from './ast/cursor';
Expand All @@ -31,7 +32,12 @@ import { templates } from './ast/templates';
export function getGraphQLCompletions(
filename: string,
cursorPosition: number,
schema: { current: GraphQLSchema | null },
schema: {
current:
| GraphQLSchema
| { schemas: { [name: string]: GraphQLSchema } }
| null;
},
info: ts.server.PluginCreateInfo
): ts.WithMetadata<ts.CompletionInfo> | undefined {
const isCallExpression = info.config.templateIsCallExpression ?? true;
Expand All @@ -46,14 +52,17 @@ export function getGraphQLCompletions(
? bubbleUpCallExpression(node)
: bubbleUpTemplate(node);

let text, cursor;
let text, cursor, schemaToUse: GraphQLSchema;
if (
ts.isCallExpression(node) &&
isCallExpression &&
templates.has(node.expression.getText()) &&
node.arguments.length > 0 &&
ts.isNoSubstitutionTemplateLiteral(node.arguments[0])
) {
const typeChecker = info.languageService.getProgram()?.getTypeChecker();
const schemaName = getSchemaName(node, typeChecker);

const foundToken = getToken(node.arguments[0], cursorPosition);
if (!schema.current || !foundToken) return undefined;

Expand All @@ -62,6 +71,10 @@ export function getGraphQLCompletions(

text = `${queryText}\n${fragments.map(x => print(x)).join('\n')}`;
cursor = new Cursor(foundToken.line, foundToken.start - 1);
schemaToUse =
'schemas' in schema.current
? schema.current.schemas[schemaName]
: schema.current;
} else if (ts.isTaggedTemplateExpression(node)) {
const { template, tag } = node;

Expand All @@ -88,12 +101,16 @@ export function getGraphQLCompletions(

text = combinedText;
cursor = new Cursor(foundToken.line, foundToken.start - 1);
schemaToUse =
'schemas' in schema.current
? schema.current.schemas['default']
: schema.current;
} else {
return undefined;
}

const [suggestions, spreadSuggestions] = getSuggestionsInternal(
schema.current,
schemaToUse,
text,
cursor
);
Expand Down
4 changes: 2 additions & 2 deletions packages/graphqlsp/src/checkImports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ function getFragmentsInSource(
const exports = typeChecker.getExportsOfModule(symbol);
const exportedNames = exports.map(symb => symb.name);
const nodes = callExpressions.nodes.filter(x => {
let parent = x.parent;
let parent = x.node.parent;
while (
parent &&
!ts.isSourceFile(parent) &&
Expand All @@ -191,7 +191,7 @@ function getFragmentsInSource(
});

nodes.forEach(node => {
const text = resolveTemplate(node, src.fileName, info).combinedText;
const text = resolveTemplate(node.node, src.fileName, info).combinedText;
try {
const parsed = parse(text, { noLocation: true });
if (parsed.definitions.every(x => x.kind === Kind.FRAGMENT_DEFINITION)) {
Expand Down
13 changes: 10 additions & 3 deletions packages/graphqlsp/src/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,13 @@ const cache = new LRUCache<number, ts.Diagnostic[]>({

export function getGraphQLDiagnostics(
filename: string,
schema: { current: GraphQLSchema | null; version: number },
schema: {
current:
| GraphQLSchema
| { schemas: { [name: string]: GraphQLSchema } }
| null;
version: number;
},
info: ts.server.PluginCreateInfo
): ts.Diagnostic[] | undefined {
const isCallExpression = info.config.templateIsCallExpression ?? true;
Expand Down Expand Up @@ -131,15 +137,16 @@ export function getGraphQLDiagnostics(
let fragmentDiagnostics: ts.Diagnostic[] = [];

if (isCallExpression) {
const persistedCalls = findAllPersistedCallExpressions(source);
const persistedCalls = findAllPersistedCallExpressions(source, info);
// We need to check whether the user has correctly inserted a hash,
// by means of providing an argument to the function and that they
// are establishing a reference to the document by means of the generic.
//
// OPTIONAL: we could also check whether the hash is out of date with the
// document but this removes support for self-generating identifiers
const persistedDiagnostics = persistedCalls
.map<ts.Diagnostic | null>(callExpression => {
.map<ts.Diagnostic | null>(found => {
const { node: callExpression } = found;
if (!callExpression.typeArguments && !callExpression.arguments[1]) {
return {
category: ts.DiagnosticCategory.Warning,
Expand Down
5 changes: 4 additions & 1 deletion packages/graphqlsp/src/graphql/getSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ async function saveTadaIntrospection(
}

export interface SchemaRef {
current: GraphQLSchema | null;
current:
| GraphQLSchema
| { schemas: { [name: string]: GraphQLSchema } }
| null;
version: number;
}

Expand Down
23 changes: 20 additions & 3 deletions packages/graphqlsp/src/quickInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
bubbleUpCallExpression,
bubbleUpTemplate,
findNode,
getSchemaName,
getSource,
} from './ast';
import { resolveTemplate } from './ast/resolve';
Expand All @@ -16,7 +17,12 @@ import { templates } from './ast/templates';
export function getGraphQLQuickInfo(
filename: string,
cursorPosition: number,
schema: { current: GraphQLSchema | null },
schema: {
current:
| GraphQLSchema
| { schemas: { [name: string]: GraphQLSchema } }
| null;
},
info: ts.server.PluginCreateInfo
): ts.QuickInfo | undefined {
const isCallExpression = info.config.templateIsCallExpression ?? true;
Expand All @@ -31,19 +37,26 @@ export function getGraphQLQuickInfo(
? bubbleUpCallExpression(node)
: bubbleUpTemplate(node);

let cursor, text;
let cursor, text, schemaToUse: GraphQLSchema;
if (
ts.isCallExpression(node) &&
isCallExpression &&
templates.has(node.expression.getText()) &&
node.arguments.length > 0 &&
ts.isNoSubstitutionTemplateLiteral(node.arguments[0])
) {
const typeChecker = info.languageService.getProgram()?.getTypeChecker();
const schemaName = getSchemaName(node, typeChecker);

const foundToken = getToken(node.arguments[0], cursorPosition);
if (!schema.current || !foundToken) return undefined;

text = node.arguments[0].getText();
cursor = new Cursor(foundToken.line, foundToken.start - 1);
schemaToUse =
'schemas' in schema.current
? schema.current.schemas[schemaName]
: schema.current;
} else if (ts.isTaggedTemplateExpression(node)) {
const { template, tag } = node;
if (!ts.isIdentifier(tag) || !templates.has(tag.text)) return undefined;
Expand All @@ -69,11 +82,15 @@ export function getGraphQLQuickInfo(
foundToken.line = foundToken.line + amountOfLines;
text = combinedText;
cursor = new Cursor(foundToken.line, foundToken.start - 1);
schemaToUse =
'schemas' in schema.current
? schema.current.schemas['default']
: schema.current;
} else {
return undefined;
}

const hoverInfo = getHoverInformation(schema.current, text, cursor);
const hoverInfo = getHoverInformation(schemaToUse, text, cursor);

return {
kind: ts.ScriptElementKind.label,
Expand Down

0 comments on commit 9dd4f5a

Please sign in to comment.