Repository navigation
feat: check GraphQL documents with TypeScript 7.1 and tsrs (checkProject) - #438
Open
JoviDeCroock wants to merge 2 commits into
Open
JoviDeCroock wants to merge 2 commits into
JoviDeCroock wants to merge 2 commits into
Conversation
Moves GraphQLSP's diagnostics onto a small host interface (GraphQLSPHost), which tsserver's PluginCreateInfo satisfies, and adds an adapter running them on the TypeScript 7.1 API (typescript@>=7.1, tsrs), exposed as checkProject() from @0no-co/graphqlsp/check.
🦋 Changeset detectedLatest commit: b423a19 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This was referenced Oct 24, 2026
JoviDeCroock
marked this pull request as ready for review
October 24, 2026 09:23
JoviDeCroock
added this pull request to stack #440
October 24, 2026 09:23
checkProject now accepts runExternalCode, which is passed to the TypeScript 7.1 API like tsc --runExternalCode, so documents imported from content-mapped files (e.g. .graphql files mapped by @gql.tada/content-mapper) are part of the program and can be resolved. Content-mapped files themselves aren't checked, since their mappers report their diagnostics. Persisted document hashes are now computed from string literals' values rather than their source text, which failed to parse documents written with escape sequences.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TypeScript 7 doesn't load language service plugins, so GraphQLSP's diagnostics currently don't run under
tsc, the TypeScript 7 language server, or tsrs. This PR moves the diagnostics onto a small host interface and adds an adapter that runs them on the TypeScript 7.1 API. It's exposed ascheckProject()from the new@0no-co/graphqlsp/checkentry.Context: 0no-co/gql.tada#606 and 0no-co/gql.tada#610 handle
.graphqlfiles through a TypeScript 7.1 content mapper. Content mappers can't register.ts/.tsx, so diagnostics for inlinegraphql()documents need this path. Stacked on top: #439 (the VS Code extension runs the same diagnostics through the TypeScript 7 extension's API session).What changed
src/host.ts). The diagnostics path (document discovery, cross-file fragment resolution, schema validation, colocation, unused fields, persisted checks) now takes aGraphQLSPHostinstead ofts.server.PluginCreateInfo. Its checker is aHostChecker, which only exposesgetSymbolAtLocation,getAliasedSymbol,getTypeAtLocation,getTypeOfSymbol,getShorthandAssignmentValueSymbol,getContextualTypeandgetExportsOfModule.PluginCreateInfosatisfies the interface as-is, so the tsserver plugin passes itsinfoalong unchanged.src/ts7/):modules.tsis the only place that loads the API. It resolvestypescriptortsrsfrom the project, or fromcompilerPath.typescript@7.1.0-betaexportstypescript/sync,typescript/astandtypescript/ast/is, whiletsrs@0.18still usestsrs/unstable/*; both layouts are detected from the package'sexports. TypeScript 5/6 is detected too, and goes through its classic compiler API.namespace.tsbuilds thetsnamespace subset GraphQLSP uses from the API's AST modules. Enums are always taken from the loaded compiler:SyntaxKindvalues differ between TS 5/6, TS 7 and tsrs (SourceFileis 308 in typescript@7.1, 307 in tsrs@0.18).checker.tswraps the API checker; see the AST/API differences below.config.tsreads the plugin entry fromtsconfig.json. TypeScript 7 strips plugin options from parsedcompilerOptions.plugins(onlynamesurvives), so raw configs are read withreadConfigFile, followingextends. The schema root is the directory of the config that declares the entry, like@gql.tada/internal.loadSchema(info, logger, { rootPath, watch, persist }). Adds a one-shot mode (no watchers, no typings writes) and an awaitableschema.ready. The defaults are unchanged.escapedText→text. The TypeScript 7.1 API's identifiers have noescapedText, so the six identifier comparisons now usetext. On TS 5/6 the two only differ for__-prefixed names, where comparingescapedText(___x) against source text never matched, so the old behaviour there was already broken.AST/API differences found (handled in the adapter)
symbol.declarations/valueDeclarationare nodesNodeHandles that must be.resolve(project)dtype.types,type.isUnionOrIntersection()type.getTypes(),isUnionType()/isIntersectionType()type.symbolis aSymbolgetSymbol())typeRef.resolvedTypeArgumentschecker.getTypeArguments(type)ts.forEachChild(node, cb)node.forEachChild(cb)identifier.escapedTextidentifier.textisStringLiteralLike,isFunctionLike,isClassLike,isObjectLiteralElement,isParameter,isMethodSignature,isPropertySignature,isCommaListExpression,idTextisClassLikeDeclaration,isParameterDeclaration, …); there's noCommaListExpressionOtherwise the node shapes match Strada closely:
kind,parent,pos/end,getStart(),getText(),getSourceFile()and named children all work. Node lists are arrays (RemoteNodeList).checkProjectAPI (for gql.tada'scheckCLI)Plain data only, so it can be serialized as-is. Checks are queued, because GraphQLSP's modules share one
tsnamespace.Tests and CI
test/e2e/fixture-project-ts7. An on-disk gql.tada project covering a cross-file fragment spread (valid.ts), schema errors and an unknown fragment, a deprecated field, a co-located fragment import that isn't spread, and unused fields. It installstypescript-7@npm:typescript@7.1.0-betaandtsrs@0.18.0-ts7.1.0-dev.20260929. They live in the fixture, not the root: at the root, the alias'stscbin replaced the workspace's TypeScript 6tsc.test/e2e/ts7.test.ts. RunscheckProjectwith the workspace's TypeScript 5/6 (inline-snapshotted), then with TypeScript 7.1 and tsrs, and asserts their diagnostics are identical to TS 5/6's.typescript-7job installstypescript@beta,typescript@nextortsrs@latestinto the fixture and runs the parity tests.nextandtsrsarecontinue-on-error. The existing TS 5.9/6.0 matrix also runs the parity tests against the pinned versions.Verified locally:
pnpm run test:e2e: 19 files, 109 tests passed (104 existing + 5 new).set-typescript-version.mjs 5.9.3plus the parity tests) passed in a clean worktree.typescript@nextleg (it resolved7.1.0-dev.20261023.1) passed.dist/check.js(CJS) anddist/check.mjs(ESM) were smoke-tested from plain Node with TS 7.1 (about 1.2 s), tsrs (about 0.8 s) and TS 6 (about 3.2 s) on the fixture, all with the same 6 diagnostics.tsc -p packages/graphqlsp --types node --module esnextreports no errors in new or changed code. The package tsconfig's existing errors are unchanged.Known gaps
@gql.tada/internal@1.2.2(the latest published version) doesrequire('typescript')at import time and usests.parseConfigFileTextToJson/ts.sys.watchFile.checkProjectavoids both, but a project with onlytsrsand notypescriptinstalled will still fail on that top-levelrequire. fix(internal): Parse tsconfig.json without the TypeScript compiler API gql.tada#573 removes the tsconfig parsing; the import still needs to go.registerLspMiddleware.