Mirror: TypeScript LSP plugin that finds GraphQL documents in your code and provides diagnostics, auto-complete and hover-information.

support updating multiple operations (#69)

* support updating multiple operaitons

* update workflows

* fixes

* remove reload tracking

* reload on error

Changed files
+69 -46
.changeset
.github
workflows
packages
example
graphqlsp
+5
.changeset/pink-papayas-relax.md
···
+
---
+
'@0no-co/graphqlsp': patch
+
---
+
+
Fix multiple selection-set updates
+3 -2
.github/workflows/ci.yaml
···
on:
pull_request:
push:
-
branches: main
+
branches:
+
- main
jobs:
check:
···
- name: Setup pnpm
uses: pnpm/action-setup@v2.2.4
with:
-
version: 7
+
version: 8.6.1
run_install: false
- name: Get pnpm store directory
+1 -1
.github/workflows/release.yaml
···
- name: Setup pnpm
uses: pnpm/action-setup@v2.2.4
with:
-
version: 8
+
version: 8.6.1
run_install: false
- name: Get pnpm store directory
+12 -12
packages/example/src/index.generated.ts
···
import * as Types from '../__generated__/baseGraphQLSP';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
-
export type PokemonsQueryVariables = Types.Exact<{ [key: string]: never }>;
+
export type PokQueryVariables = Types.Exact<{ [key: string]: never }>;
-
export type PokemonsQuery = {
-
__typename?: 'Query';
+
export type PokQuery = {
+
__typename: 'Query';
pokemons?: Array<{
__typename: 'Pokemon';
id: string;
···
} | null> | null;
};
-
export type PokemonQueryVariables = Types.Exact<{
+
export type PoQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
-
export type PokemonQuery = {
-
__typename?: 'Query';
+
export type PoQuery = {
+
__typename: 'Query';
pokemon?: {
__typename: 'Pokemon';
id: string;
···
} | null;
};
-
export const PokemonsDocument = {
+
export const PokDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
-
name: { kind: 'Name', value: 'Pokemons' },
+
name: { kind: 'Name', value: 'Pok' },
selectionSet: {
kind: 'SelectionSet',
selections: [
···
},
},
],
-
} as unknown as DocumentNode<PokemonsQuery, PokemonsQueryVariables>;
-
export const PokemonDocument = {
+
} as unknown as DocumentNode<PokQuery, PokQueryVariables>;
+
export const PoDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
-
name: { kind: 'Name', value: 'Pokemon' },
+
name: { kind: 'Name', value: 'Po' },
variableDefinitions: [
{
kind: 'VariableDefinition',
···
},
},
],
-
} as unknown as DocumentNode<PokemonQuery, PokemonQueryVariables>;
+
} as unknown as DocumentNode<PoQuery, PoQueryVariables>;
+4 -4
packages/example/src/index.ts
···
import { Pokemon, PokemonFields } from './Pokemon';
const PokemonsQuery = gql`
-
query Pokemons {
+
query Pok {
pokemons {
id
name
···
fleeRate
}
}
-
` as typeof import('./index.generated').PokemonsDocument;
+
` as typeof import('./index.generated').PokDocument;
const client = createClient({
url: '',
···
});
const PokemonQuery = gql`
-
query Pokemon($id: ID!) {
+
query Po($id: ID!) {
pokemon(id: $id) {
id
fleeRate
__typename
}
}
-
` as typeof import('./index.generated').PokemonDocument;
+
` as typeof import('./index.generated').PoDocument;
client
.query(PokemonQuery, { id: '' })
+37 -24
packages/graphqlsp/src/diagnostics.ts
···
const shouldCheckForColocatedFragments =
info.config.shouldCheckForColocatedFragments || true;
-
const source = getSource(info, filename);
+
let source = getSource(info, filename);
if (!source) return undefined;
const nodes = findAllTaggedTemplateNodes(source);
···
scalars,
baseTypesPath
).then(() => {
+
source = getSource(info, filename);
+
if (!source) return undefined;
+
if (isFileDirty(filename, source) && !hasTSErrors) {
return;
}
-
nodes.forEach((node, i) => {
+
let addedLength = 0;
+
const finalSourceText = nodes.reduce((sourceText, node, i) => {
+
source = getSource(info, filename);
+
if (!source) return sourceText;
+
const queryText = texts[i] || '';
const parsed = parse(queryText, { noLocation: true });
const isFragment = parsed.definitions.every(
···
name = operationNode.name?.value || '';
}
-
if (!name) return;
+
if (!name) return sourceText;
name = name.charAt(0).toUpperCase() + name.slice(1);
const parentChildren = node.parent.getChildren();
···
: `${name}Document`;
let imp = ` as typeof import('./${nameParts
.join('.')
-
.replace('.ts', '')}').${exportName}`;
+
.replace('.ts', '')}').${exportName}\n`;
// This checks whether one of the children is an import-type
// which is a short-circuit if there is no as
···
isImportTypeNode(x)
) as ImportTypeNode;
-
if (typeImport && typeImport.getText().includes(exportName)) return;
+
if (typeImport && typeImport.getText().includes(exportName))
+
return sourceText;
const span = { length: 1, start: node.end };
···
// but ` as ` meaning we need to keep that part
// around.
imp = imp.slice(4);
-
text = source.text.replace(typeImport.getText(), imp);
+
// We remove the \n
+
imp = imp.substring(0, imp.length - 1);
+
text = sourceText.replace(typeImport.getText(), imp);
span.length =
imp.length + ((oldExportName || '').length - exportName.length);
} else {
text =
-
source.text.substring(0, span.start) +
+
sourceText.substring(0, span.start) +
imp +
-
source.text.substring(
-
span.start + span.length,
-
source.text.length
-
);
+
sourceText.substring(span.start + span.length, sourceText.length);
}
-
const scriptInfo =
-
info.project.projectService.getScriptInfo(filename);
-
const snapshot = scriptInfo!.getSnapshot();
+
sourceText = text;
+
addedLength = addedLength + imp.length;
+
// @ts-ignore
+
source.hasBeenIncrementallyParsed = false;
+
source.update(text, { span, newLength: imp.length });
+
source.text = text;
+
+
return sourceText;
+
}, source.text);
-
source.update(text, { span, newLength: imp.length });
-
scriptInfo!.editContent(0, snapshot.getLength(), text);
-
info.languageServiceHost.writeFile!(source.fileName, text);
-
if (!!typeImport) {
-
// To update the types, otherwise data is stale
-
scriptInfo!.reloadFromFile();
-
}
-
scriptInfo!.registerFileUpdate();
-
});
+
const scriptInfo = info.project.projectService.getScriptInfo(filename);
+
const snapshot = scriptInfo!.getSnapshot();
+
scriptInfo!.editContent(0, snapshot.getLength(), finalSourceText);
+
info.languageServiceHost.writeFile!(source.fileName, finalSourceText);
+
scriptInfo!.reloadFromFile();
+
scriptInfo!.registerFileUpdate();
});
-
} catch (e) {}
+
} catch (e) {
+
const scriptInfo = info.project.projectService.getScriptInfo(filename);
+
scriptInfo!.reloadFromFile();
+
}
}
return tsDiagnostics;
+7 -3
pnpm-lock.yaml
···
-
lockfileVersion: '6.0'
+
lockfileVersion: '6.1'
+
+
settings:
+
autoInstallPeers: true
+
excludeLinksFromLockfile: false
importers:
···
'@graphql-codegen/plugin-helpers': 4.2.0(graphql@16.6.0)
graphql: 16.6.0
tslib: 2.5.0
-
dev: false
/@graphql-codegen/core@3.1.0(graphql@16.6.0):
resolution: {integrity: sha512-DH1/yaR7oJE6/B+c6ZF2Tbdh7LixF1K8L+8BoSubjNyQ8pNwR4a70mvc1sv6H7qgp6y1bPQ9tKE+aazRRshysw==}
···
resolution: {directory: packages/graphqlsp, type: directory}
id: file:packages/graphqlsp
name: '@0no-co/graphqlsp'
-
version: 0.1.0
+
version: 0.7.1
dependencies:
+
'@graphql-codegen/add': 4.0.1(graphql@16.6.0)
'@graphql-codegen/core': 3.1.0(graphql@16.6.0)
'@graphql-codegen/typed-document-node': 3.0.2(graphql@16.6.0)
'@graphql-codegen/typescript': 3.0.3(graphql@16.6.0)