graphqlsp

repository·main·Indexed 19 days ago

https://github.com/0no-co/graphqlsp

A TypeScript LSP plugin (version 0.2.0) that identifies GraphQL documents in code to provide hover information, schema mismatch diagnostics, and auto-completion for GraphQL fields. It can be configured via tsconfig.json and supports features like unused field tracking, fragment co-location, and integration with gql.tada.

Tokens
5K
Snippets
18
Records
30
Agent score
66%

What's inside graphqlsp

  1. How unused field tracking works

    main

    The trackFieldUsage feature encourages fragment co-location by tracking results and accessed properties within the same file.

    Limitations:

    • It does not track mutations or subscriptions, as these are often used for normalized cache updates where extra fields are intentionally added.
  2. Install @0no-co/graphqlsp

    main

    Install the GraphQLSP TypeScript LSP plugin as a development dependency using npm.

    npm install -D @0no-co/graphqlsp
  3. Debug GraphQLSP with breakpoints

    main

    To debug the plugin source code in packages/graphqlsp/src, follow these steps:

    1. Build the development version: Run pnpm --filter @0no-co/graphqlsp dev (or NODE_ENV=development pnpm --filter @0no-co/graphqlsp build) to ensure sourcemaps are emitted.
    2. Launch the example: Open the example project with the debug port prefix: TSS_DEBUG_BRK=9559 code packages/example and run pnpm i.
    3. Attach Debugger: In your main VS Code window (the repo root), set breakpoints in packages/graphqlsp/src and use the "Attach to VS Code TS Server via Port" launch configuration.
    4. Trigger: Perform an action in the example window (like typing in a graphql() document) to hit the breakpoint.

    Note: After making changes to the plugin, restart the TypeScript server in the example window and reattach the debugger.

  4. Configure GraphQLSP in tsconfig.json

    main

    To activate the plugin, add it to the plugins array in your tsconfig.json. You must provide a schema pointing to your .graphql or .json schema file or a schema URL.

    Note: Ensure your editor (like VSCode) is using the Workspace Version of TypeScript. In VSCode, you can configure this by creating a .vscode/config.json file in your project root:

    {
      "compilerOptions": {
        "plugins": [
          {
            "name": "@0no-co/graphqlsp",
            "schema": "./schema.graphql"
          }
        ]
      }
    }
    // .vscode/config.json
    {
      "typescript.tsdk": "node_modules/typescript/lib",
      "typescript.enablePromptUseWorkspaceTsdk": true
    }
  5. Configure GraphQLSP plugin options

    main

    The plugin accepts several configuration options within the tsconfig.json plugin object.

    Required

    • schema: A URL, .json file, or .graphql file. To use custom headers for introspection, use the object notation: { "url": "...", "headers": { "Authorization": "..." } }.

    Optional

    • template: Additional templates to add to the defaults gql and graphql.
    • templateIsCallExpression: Set to false if you are using tagged template literals instead of graphql('doc') (default: true).
    • shouldCheckForColocatedFragments: Scans imports to find unused fragments (only works with call-expressions, default: true).
    • trackFieldUsage: Warns about unused fields within the same file (only works with the client-preset and call-expressions, default: true).
    • tadaOutputLocation: Directory for gql.tada to automatically generate an introspection.ts file.
    • tadaDisablePreprocessing: Disables optimization of tadaOutput to a pre-processed TypeScript type (default: false).
    • clientDirectives: Specify additional clientDirectives to prevent them from being flagged as missing schema-directives.
  6. How GraphQL autocomplete suggestions are generated

    main

    The getSuggestionsInternal function implements the core logic for generating GraphQL autocomplete suggestions. It uses a combination of the graphql-language-service and local AST analysis to ensure suggestions are contextually relevant.

    Key Logic Steps

    1. Token Analysis: It identifies the current token at the cursor position using getTokenAtPosition.
    2. Fragment Handling: It parses the query text to find existing FRAGMENT_DEFINITION nodes to ensure fragment spreads are handled correctly.
    3. Contextual Filtering:
      • Type Conditions: If the cursor is on an on keyword (e.g., in an inline fragment ... on Type), it adjusts the parser state to suggest fields for that specific type.
      • Arguments: If the cursor is within an argument list, it filters out arguments that have already been used in the current field.
      • Fields: If the cursor is in a selection set, it filters out fields that are already present in the current selection set.
      • Fragment Spreads: It prevents suggesting fragment spreads that are already being used within the current parent definition.
    4. Suggestion Types:
      • suggestions: Standard GraphQL fields, arguments, or type names.
      • spreadSuggestions: Specifically for fragment spreads (e.g., ...FragmentName).
  7. Manual Fragment Masking with TypedDocumentNode

    main

    If your environment doesn't support fragment typing out of the box, you can manually cast types using TypedDocumentNode. This is useful for ensuring useFragment receives the correct type.

    import { TypedDocumentNode } from '@graphql-typed-document-node/core';
    
    export const PokemonFields = gql`
      fragment pokemonFields on Pokemon {
        id
        name
      }
    ` as typeof import('./Pokemon.generated').PokemonFieldsFragmentDoc;
    
    export const Pokemon = props => {
      const pokemon = useFragment(props.pokemon, PokemonFields);
    };
    
    export function useFragment<Type>(
      data: any,
      _fragment: TypedDocumentNode<Type>
    ): Type {
      return data;
    }
  8. Configure the GraphQLSP TypeScript LSP Plugin

    main

    GraphQLSP is a TypeScript Language Service Plugin. You configure it via the info.config object provided by the TypeScript server. The configuration allows you to define schema origins, custom templates, and behavior for field tracking and fragment masking.

    Configuration Options

    KeyTypeDescription
    schemaSchemaOriginThe primary schema origin for the plugin.
    schemasSchemaOrigin[]An array of additional schema origins.
    tadaDisablePreprocessingboolean (optional)If true, disables preprocessing logic.
    templateIsCallExpressionboolean (optional)Indicates if the GraphQL template is a call expression.
    shouldCheckForColocatedFragmentsboolean (optional)Whether to check for colocated fragments.
    templatestring (optional)A custom template string to use for GraphQL operations.
    clientDirectivesstring[] (optional)A list of client-side directives to recognize.
    trackFieldUsageboolean (optional)Enables tracking of unused fields.
    tadaOutputLocationstring (optional)The location where generated types/files should be placed.
  9. Configure GraphQL document search mode

    main

    GraphQLSP can search for GraphQL documents in two modes: as tagged template literals (e.g., gql```) or as function call expressions (e.g., gql(...)`).

    If you are seeing a MODE_MISMATCH_CODE warning, you need to adjust the templateIsCallExpression setting in your tsconfig.json:

    • Set "templateIsCallExpression": false to search for tagged templates.
    • Set "templateIsCallExpression": true (default) to search for call expressions.
  10. Configure client directives in GraphQLSP

    main
    By default, GraphQLSP recognizes a set of base client directives (e.g., @client, @unmask, @relay). You can extend this list by providing clientDirectives in your plugin configuration within tsconfig.json. This prevents the plugin from reporting 'Unknown directive' errors for your custom client-side directives.
  11. Configure unused field tracking

    main

    GraphQLSP can automatically detect and warn you about GraphQL fields that are requested in a document but never actually accessed in your TypeScript code. This is controlled via the plugin configuration.

    To disable this feature, set trackFieldUsage to false. You can also specify reservedKeys to prevent certain fields from being flagged as unused (e.g., internal metadata or specific IDs).

    By default, trackFieldUsage is true, and the following keys are always reserved:

    • id
    • _id
    • __typename
    {
      "trackFieldUsage": true,
      "reservedKeys": ["myCustomKey"]
    }
  12. Troubleshoot persisted query errors

    main

    When using persisted queries, GraphQLSP validates the relationship between the hash and the document. Common diagnostic issues include:

    • MISSING_PERSISTED_TYPE_ARG: The generic pointing to the GraphQL document is missing or is not a typeQueryNode (e.g., it should be graphql.persisted<typeof document>).
    • MISSING_PERSISTED_DOCUMENT: The plugin cannot find a reference to the document being persisted.
    • MISSING_PERSISTED_CODE_ARG: The call expression is missing the hash argument required for the persisted call.
    • MISSMATCH_HASH_TO_DOCUMENT: The provided hash (e.g., sha256:...) does not match the hash generated from the actual document content.