Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support queries for multiple schemas in the same file #3411

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 31 additions & 8 deletions packages/graphql-language-service-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Supported features include:
- Support for `gql` `graphql` and other template tags inside javascript,
typescript, jsx, ts, vue and svelte files, and an interface to allow custom
parsing of all files.
- Support multiple GraphQL APIs in the same file via annotation suffixes.

## Installation and Usage

Expand Down Expand Up @@ -187,6 +188,9 @@ export default {
languageService: {
cacheSchemaFileForLookup: true,
enableValidation: false,
gqlTagOptions: {
annotationSuffix: 'my-project',
},
},
},
},
Expand Down Expand Up @@ -237,18 +241,37 @@ via `initializationOptions` in nvim.coc. The options are mostly designed to
configure graphql-config's load parameters, the only thing we can't configure
with graphql config. The final option can be set in `graphql-config` as well

| Parameter | Default | Description |
| ----------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `graphql-config.load.baseDir` | workspace root or process.cwd() | the path where graphql config looks for config files |
| `graphql-config.load.filePath` | `null` | exact filepath of the config file. |
| `graphql-config.load.configName` | `graphql` | config name prefix instead of `graphql` |
| `graphql-config.load.legacy` | `true` | backwards compatibility with `graphql-config@2` |
| `graphql-config.dotEnvPath` | `null` | backwards compatibility with `graphql-config@2` |
| `vscode-graphql.cacheSchemaFileForLookup` | `false` | generate an SDL file based on your graphql-config schema configuration for schema definition lookup and other features. useful when your `schema` config are urls |
| Parameter | Default | Description |
| ----------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `graphql-config.load.baseDir` | workspace root or process.cwd() | the path where graphql config looks for config files |
frandiox marked this conversation as resolved.
Show resolved Hide resolved
| `graphql-config.load.filePath` | `null` | exact filepath of the config file. |
| `graphql-config.load.configName` | `graphql` | config name prefix instead of `graphql` |
| `graphql-config.load.legacy` | `true` | backwards compatibility with `graphql-config@2` |
| `graphql-config.dotEnvPath` | `null` | backwards compatibility with `graphql-config@2` |
| `vscode-graphql.cacheSchemaFileForLookup` | `false` | generate an SDL file based on your graphql-config schema configuration for schema definition lookup and other features. useful when your `schema` config are urls |
| `vscode-graphql.gqlTagOptions.annotationSuffix` | `null` | establish a suffix to match queries to a project schema using `#graphql:<suffix>` comment. Only the first matching project for a given query is used, thus supporting multiple queries for different schemas in the same file |

all the `graphql-config.load.*` configuration values come from static
`loadConfig()` options in graphql config.

Use the `gqlTagOptions.annotationSuffix` option to mix queries for different schemas in the same file. Each query annotated with the `#graphql:<suffix>` comment will be matched to the first project with the same suffix:

```ts
// file.js

const queryForDefaultProject = `#graphql
query { something }
`;

const queryForDbProject = `#graphql:db
query { something }
`;

const queryForCmsProject = `#graphql:cms
query { something }
`;
```

(more coming soon!)

### Architectural Overview
Expand Down
3 changes: 2 additions & 1 deletion packages/graphql-language-service-server/src/GraphQLCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import glob from 'glob';
import { LoadConfigOptions } from './types';
import { URI } from 'vscode-uri';
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
import { EXTENSION_NAME } from './GraphQLLanguageService';
import {
DEFAULT_SUPPORTED_EXTENSIONS,
DEFAULT_SUPPORTED_GRAPHQL_EXTENSIONS,
Expand All @@ -59,7 +60,7 @@ const LanguageServiceExtension: GraphQLExtensionDeclaration = api => {
// For documents
api.loaders.documents.register(new CodeFileLoader());

return { name: 'languageService' };
return { name: EXTENSION_NAME };
};

// Maximum files to read when processing GraphQL files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
SymbolKind,
} from 'vscode-languageserver-types';

import { fileURLToPath } from 'node:url';

export const EXTENSION_NAME = 'languageService';

const KIND_TO_SYMBOL_KIND: { [key: string]: SymbolKind } = {
[Kind.FIELD]: SymbolKind.Field,
[Kind.OPERATION_DEFINITION]: SymbolKind.Class,
Expand Down Expand Up @@ -84,6 +88,10 @@
return KIND_TO_SYMBOL_KIND[tree.kind];
}

function normalizeUri(uri: string) {
return uri.startsWith('file:') ? fileURLToPath(uri) : uri;
}

export class GraphQLLanguageService {
_graphQLCache: GraphQLCache;
_graphQLConfig: GraphQLConfig;
Expand All @@ -96,11 +104,41 @@
this._logger = logger;
}

getConfigForURI(uri: Uri) {
const config = this._graphQLCache.getProjectForFile(uri);
if (config) {
return config;
getAllProjectsForFile(uri: Uri) {
const filePath = normalizeUri(uri);
const projects = Object.values(this._graphQLConfig.projects).filter(
project => project.match(filePath),
);

return projects.length > 0
? projects
: // Fallback, this always finds at least 1 project
[this._graphQLConfig.getProjectForFile(filePath)];
}

getProjectForQuery(
query: string,
uri: Uri,
projects?: GraphQLProjectConfig[],
) {
if (!query.startsWith('#graphql')) {
// Query is not annotated with #graphql.
// Skip suffix check and return the first project that matches the file.
return (
projects?.[0] ??
this._graphQLConfig.getProjectForFile(normalizeUri(uri))
);
}

return (projects || this.getAllProjectsForFile(uri)).find(project => {
const ext = project.hasExtension(EXTENSION_NAME)
? project.extension(EXTENSION_NAME)
: null;

const suffix = ext?.gqlTagOptions?.annotationSuffix;

return query.startsWith(`#graphql${suffix ? ':' + suffix : ''}\n`);
});
}

public async getDiagnostics(
Expand All @@ -111,7 +149,7 @@
// Perform syntax diagnostics first, as this doesn't require
// schema/fragment definitions, even the project configuration.
let documentHasExtensions = false;
const projectConfig = this.getConfigForURI(uri);
const projectConfig = this.getProjectForQuery(document, uri);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you rename this to getProjectForDocument ? just so people don't assume this only works with queries, as it should also work with SDL

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! I've also resolved the new conflicts but fyi, there's a problem with cspell on commit because it finds unknown words in .vscode/settings.json.

// skip validation when there's nothing to validate, prevents noisy unexpected EOF errors
if (!projectConfig || !document || document.trim().length < 2) {
return [];
Expand Down Expand Up @@ -218,7 +256,7 @@
position: IPosition,
filePath: Uri,
): Promise<Array<CompletionItem>> {
const projectConfig = this.getConfigForURI(filePath);
const projectConfig = this.getProjectForQuery(query, filePath);

Check warning on line 259 in packages/graphql-language-service-server/src/GraphQLLanguageService.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L259 was not covered by tests
if (!projectConfig) {
return [];
}
Expand Down Expand Up @@ -255,7 +293,7 @@
filePath: Uri,
options?: HoverConfig,
): Promise<Hover['contents']> {
const projectConfig = this.getConfigForURI(filePath);
const projectConfig = this.getProjectForQuery(query, filePath);
if (!projectConfig) {
return '';
}
Expand All @@ -272,7 +310,7 @@
position: IPosition,
filePath: Uri,
): Promise<DefinitionQueryResult | null> {
const projectConfig = this.getConfigForURI(filePath);
const projectConfig = this.getProjectForQuery(query, filePath);
if (!projectConfig) {
return null;
}
Expand Down