From 17fa628caa4f926d938a17d3c73a1c554d3d89fa Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Tue, 5 Dec 2023 07:06:51 +0100 Subject: [PATCH 1/2] remove co-located fragment checks for now --- .changeset/calm-bears-argue.md | 5 ++ README.md | 4 +- packages/graphqlsp/README.md | 3 - packages/graphqlsp/src/diagnostics.ts | 114 +------------------------- packages/graphqlsp/src/index.ts | 1 - 5 files changed, 8 insertions(+), 119 deletions(-) create mode 100644 .changeset/calm-bears-argue.md diff --git a/.changeset/calm-bears-argue.md b/.changeset/calm-bears-argue.md new file mode 100644 index 00000000..31a7004d --- /dev/null +++ b/.changeset/calm-bears-argue.md @@ -0,0 +1,5 @@ +--- +'@0no-co/graphqlsp': minor +--- + +Remove the co-located fragments check for the time being as it's broken in newer TS versions and breaks with barrel files diff --git a/README.md b/README.md index 5bcc208f..f106b383 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ auto-complete and automatically generating [Typed-Document-nodes](https://the-gu - Diagnostics for adding fields that don't exist, are deprecated, missmatched argument types, ... - Auto-complete inside your editor for fields - When you save it will generate `typed-document-nodes` for your documents and cast them to the correct type -- Will warn you when you are importing from a file that is exporting fragments that you're not using ## Installation @@ -41,6 +40,7 @@ when on a TypeScript file or adding a file like [this](https://github.com/0no-co > If you are using VSCode ensure that your editor is using [the Workspace Version of TypeScript](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript) > this can be done by manually selecting it or adding a `.vscode/config.json` with the contents of +> > ```json > { > "typescript.tsdk": "node_modules/typescript/lib", @@ -62,8 +62,6 @@ when on a TypeScript file or adding a file like [this](https://github.com/0no-co - `disableTypegen` disables type-generation in general, this could be needed if offset bugs are introduced - `scalars` allows you to pass an object of scalars that we'll feed into `graphql-code-generator` - `extraTypes` allows you to specify imports or declare types to help with `scalar` definitions -- `shouldCheckForColocatedFragments` when turned on, this will scan your imports to find - unused fragments and provide a message notifying you about them ### GraphQL Code Generator client-preset diff --git a/packages/graphqlsp/README.md b/packages/graphqlsp/README.md index bf025e63..6c7b8dce 100644 --- a/packages/graphqlsp/README.md +++ b/packages/graphqlsp/README.md @@ -10,7 +10,6 @@ auto-complete and automatically generating [Typed-Document-nodes](https://the-gu - Diagnostics for adding fields that don't exist, are deprecated, missmatched argument types, ... - Auto-complete inside your editor for fields - When you save it will generate `typed-document-nodes` for your documents and cast them to the correct type -- Will warn you when you are importing from a file that is exporting fragments that you're not using ## Installation @@ -55,8 +54,6 @@ when on a TypeScript file or adding a file like [this](https://github.com/0no-co - `disableTypegen` disables type-generation in general, this could be needed if offset bugs are introduced - `scalars` allows you to pass an object of scalars that we'll feed into `graphql-code-generator` - `extraTypes` allows you to specify imports or declare types to help with `scalar` definitions -- `shouldCheckForColocatedFragments` when turned on, this will scan your imports to find - unused fragments and provide a message notifying you about them ### GraphQL Code Generator client-preset diff --git a/packages/graphqlsp/src/diagnostics.ts b/packages/graphqlsp/src/diagnostics.ts index 7faae61b..879fa97b 100644 --- a/packages/graphqlsp/src/diagnostics.ts +++ b/packages/graphqlsp/src/diagnostics.ts @@ -268,121 +268,11 @@ const runDiagnostics = ( typeof diag.code === 'number' ? diag.code : diag.severity === 2 - ? USING_DEPRECATED_FIELD_CODE - : SEMANTIC_DIAGNOSTIC_CODE, + ? USING_DEPRECATED_FIELD_CODE + : SEMANTIC_DIAGNOSTIC_CODE, messageText: diag.message.split('\n')[0], })); - const importDiagnostics = checkImportsForFragments(source, info); - - return [...tsDiagnostics, ...importDiagnostics]; -}; - -const checkImportsForFragments = ( - source: ts.SourceFile, - info: ts.server.PluginCreateInfo -) => { - const imports = findAllImports(source); - - const shouldCheckForColocatedFragments = - info.config.shouldCheckForColocatedFragments ?? false; - const tsDiagnostics: ts.Diagnostic[] = []; - if (imports.length && shouldCheckForColocatedFragments) { - const typeChecker = info.languageService.getProgram()?.getTypeChecker(); - imports.forEach(imp => { - if (!imp.importClause) return; - - const importedNames: string[] = []; - if (imp.importClause.name) { - importedNames.push(imp.importClause?.name.text); - } - - if ( - imp.importClause.namedBindings && - ts.isNamespaceImport(imp.importClause.namedBindings) - ) { - // TODO: we might need to warn here when the fragment is unused as a namespace import - return; - } else if ( - imp.importClause.namedBindings && - ts.isNamedImportBindings(imp.importClause.namedBindings) - ) { - imp.importClause.namedBindings.elements.forEach(el => { - importedNames.push(el.name.text); - }); - } - - const symbol = typeChecker?.getSymbolAtLocation(imp.moduleSpecifier); - if (!symbol) return; - - const moduleExports = typeChecker?.getExportsOfModule(symbol); - if (!moduleExports) return; - - const missingImports = moduleExports - .map(exp => { - if (importedNames.includes(exp.name)) { - return; - } - - const declarations = exp.getDeclarations(); - const declaration = declarations?.find(x => { - // TODO: check whether the sourceFile.fileName resembles the module - // specifier - return true; - }); - - if (!declaration) return; - - const [template] = findAllTaggedTemplateNodes(declaration); - if (template) { - let node = template; - if ( - ts.isNoSubstitutionTemplateLiteral(node) || - ts.isTemplateExpression(node) - ) { - if (ts.isTaggedTemplateExpression(node.parent)) { - node = node.parent; - } else { - return; - } - } - - const text = resolveTemplate( - node, - node.getSourceFile().fileName, - info - ).combinedText; - try { - const parsed = parse(text, { noLocation: true }); - if ( - parsed.definitions.every( - x => x.kind === Kind.FRAGMENT_DEFINITION - ) - ) { - return `'${exp.name}'`; - } - } catch (e) { - return; - } - } - }) - .filter(Boolean); - - if (missingImports.length) { - tsDiagnostics.push({ - file: source, - length: imp.getText().length, - start: imp.getStart(), - category: ts.DiagnosticCategory.Message, - code: MISSING_FRAGMENT_CODE, - messageText: `Missing Fragment import(s) ${missingImports.join( - ', ' - )} from ${imp.moduleSpecifier.getText()}.`, - }); - } - }); - } - return tsDiagnostics; }; diff --git a/packages/graphqlsp/src/index.ts b/packages/graphqlsp/src/index.ts index 9c456b11..861fd66e 100644 --- a/packages/graphqlsp/src/index.ts +++ b/packages/graphqlsp/src/index.ts @@ -27,7 +27,6 @@ type Config = { disableTypegen?: boolean; extraTypes?: string; scalars?: Record; - shouldCheckForColocatedFragments?: boolean; }; function create(info: ts.server.PluginCreateInfo) { From e7c2c0ed6750c8afb6e22b2d5d84340889f03d2c Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Tue, 5 Dec 2023 07:10:07 +0100 Subject: [PATCH 2/2] remove test --- test/e2e/fragments.test.ts | 133 ------------------------------------- 1 file changed, 133 deletions(-) delete mode 100644 test/e2e/fragments.test.ts diff --git a/test/e2e/fragments.test.ts b/test/e2e/fragments.test.ts deleted file mode 100644 index 040c1ec0..00000000 --- a/test/e2e/fragments.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { expect, afterAll, beforeAll, it, describe } from 'vitest'; -import { TSServer } from './server'; -import path from 'node:path'; -import fs from 'node:fs'; -import url from 'node:url'; -import ts from 'typescript/lib/tsserverlibrary'; -import { waitForExpect } from './util'; - -const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); - -const projectPath = path.resolve(__dirname, 'fixture-project'); -describe('Fragments', () => { - const outFilePost = path.join(projectPath, 'Post.ts'); - const outFilePosts = path.join(projectPath, 'Posts.ts'); - const genFilePost = path.join(projectPath, 'Post.generated.ts'); - const genFilePosts = path.join(projectPath, 'Posts.generated.ts'); - const baseGenFile = path.join(projectPath, '__generated__/baseGraphQLSP.ts'); - - let server: TSServer; - beforeAll(async () => { - server = new TSServer(projectPath, { debugLog: false }); - }); - - afterAll(() => { - try { - fs.unlinkSync(outFilePost); - fs.unlinkSync(outFilePosts); - fs.unlinkSync(genFilePost); - fs.unlinkSync(genFilePosts); - fs.unlinkSync(baseGenFile); - } catch {} - }); - - it('should send a message for missing fragment import', async () => { - server.sendCommand('open', { - file: outFilePost, - fileContent: '// empty', - scriptKindName: 'TS', - } satisfies ts.server.protocol.OpenRequestArgs); - - server.sendCommand('open', { - file: outFilePosts, - fileContent: '// empty', - scriptKindName: 'TS', - } satisfies ts.server.protocol.OpenRequestArgs); - - server.sendCommand('updateOpen', { - openFiles: [ - { - file: outFilePosts, - fileContent: fs.readFileSync( - path.join(projectPath, 'fixtures/Posts.ts'), - 'utf-8' - ), - }, - { - file: outFilePost, - fileContent: fs.readFileSync( - path.join(projectPath, 'fixtures/Post.ts'), - 'utf-8' - ), - }, - ], - } satisfies ts.server.protocol.UpdateOpenRequestArgs); - - server.sendCommand('saveto', { - file: outFilePost, - tmpfile: outFilePost, - } satisfies ts.server.protocol.SavetoRequestArgs); - - server.sendCommand('saveto', { - file: outFilePosts, - tmpfile: outFilePosts, - } satisfies ts.server.protocol.SavetoRequestArgs); - - await waitForExpect(() => { - expect(fs.readFileSync(outFilePosts, 'utf-8')).toContain( - `as typeof import('./Posts.generated').PostsListDocument` - ); - }); - - await waitForExpect(() => { - const generatedPostsFileContents = fs.readFileSync(genFilePosts, 'utf-8'); - expect(generatedPostsFileContents).toContain( - 'export const PostsListDocument = ' - ); - expect(generatedPostsFileContents).toContain( - 'import * as Types from "./__generated__/baseGraphQLSP"' - ); - }); - - await waitForExpect(() => { - expect(fs.readFileSync(outFilePost, 'utf-8')).toContain( - `as typeof import('./Post.generated').PostFieldsFragmentDoc` - ); - }); - - await waitForExpect(() => { - const generatedPostFileContents = fs.readFileSync(genFilePost, 'utf-8'); - expect(generatedPostFileContents).toContain( - 'export const PostFieldsFragmentDoc = ' - ); - expect(generatedPostFileContents).toContain( - 'import * as Types from "./__generated__/baseGraphQLSP"' - ); - }); - - const res = server.responses - .reverse() - .find( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body.file === outFilePosts - ); - - expect(res?.body.diagnostics).toEqual([ - { - category: 'message', - code: 52003, - end: { - line: 2, - offset: 31, - }, - start: { - line: 2, - offset: 1, - }, - text: 'Missing Fragment import(s) \'PostFields\' from "./Post".', - }, - ]); - }, 30000); -});