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

fix: only warn for exported fragments (#230)

Changed files
+51 -4
.changeset
packages
example-tada
src
graphqlsp
+5
.changeset/cyan-guests-share.md
···
+
---
+
'@0no-co/graphqlsp': patch
+
---
+
+
Only warn for fragments that are exported
+2 -1
packages/example-tada/src/index.tsx
···
pokemon(id: $id) {
id
fleeRate
-
...pokemonFields
...Pok
+
...pokemonFields
attacks {
special {
name
···
const { pokemon } = result.data || {};
console.log(pokemon?.weight?.maximum)
+
// @ts-ignore
return <Pokemon data={result.data!.pokemon} />;
}
+44 -3
packages/graphqlsp/src/checkImports.ts
···
import { findAllCallExpressions, findAllImports, getSource } from './ast';
import { resolveTemplate } from './ast/resolve';
+
import {
+
VariableDeclaration,
+
VariableStatement,
+
isSourceFile,
+
} from 'typescript';
export const MISSING_FRAGMENT_CODE = 52003;
···
{ start: number; length: number; fragments: Array<string> }
> => {
const imports = findAllImports(source);
+
const typeChecker = info.languageService.getProgram()?.getTypeChecker();
+
const importSpecifierToFragments: Record<
string,
{ start: number; length: number; fragments: Array<string> }
> = {};
+
+
if (!typeChecker) return importSpecifierToFragments;
if (imports.length) {
imports.forEach(imp => {
···
const externalSource = getSource(info, def.fileName);
if (!externalSource) return;
-
const fragmentsForImport = getFragmentsInSource(externalSource, info);
+
const fragmentsForImport = getFragmentsInSource(
+
externalSource,
+
typeChecker,
+
info
+
);
const names = fragmentsForImport.map(fragment => fragment.name.value);
if (
···
const externalSource = getSource(info, def.fileName);
if (!externalSource) return;
-
const fragmentsForImport = getFragmentsInSource(externalSource, info);
+
const fragmentsForImport = getFragmentsInSource(
+
externalSource,
+
typeChecker,
+
info
+
);
const names = fragmentsForImport.map(fragment => fragment.name.value);
if (
names.length &&
···
const fragmentsForImport = getFragmentsInSource(
externalSource,
+
typeChecker,
info
);
const names = fragmentsForImport.map(
···
function getFragmentsInSource(
src: ts.SourceFile,
+
typeChecker: ts.TypeChecker,
info: ts.server.PluginCreateInfo
): Array<FragmentDefinitionNode> {
let fragments: Array<FragmentDefinitionNode> = [];
const callExpressions = findAllCallExpressions(src, info, false);
-
callExpressions.nodes.forEach(node => {
+
const symbol = typeChecker.getSymbolAtLocation(src);
+
if (!symbol) return [];
+
+
const exports = typeChecker.getExportsOfModule(symbol);
+
const exportedNames = exports.map(symb => symb.name);
+
const nodes = callExpressions.nodes.filter(x => {
+
let parent = x.parent;
+
while (
+
parent &&
+
!ts.isSourceFile(parent) &&
+
!ts.isVariableDeclaration(parent)
+
) {
+
parent = parent.parent;
+
}
+
+
if (ts.isVariableDeclaration(parent)) {
+
return exportedNames.includes(parent.name.getText());
+
} else {
+
return false;
+
}
+
});
+
+
nodes.forEach(node => {
const text = resolveTemplate(node, src.fileName, info).combinedText;
try {
const parsed = parse(text, { noLocation: true });