Skip to content

[plugin-helpers] fix: satisfy strict mode in plugin-helpers (#4683) - #11019

Merged
eddeee888 merged 5 commits into
masterfrom
claude/youthful-cerf-dz8xxc
Oct 1, 2026
Merged

eddeee888 merged 5 commits into
masterfrom
claude/youthful-cerf-dz8xxc

Conversation

@eddeee888

Copy link
Copy Markdown
Collaborator

Description

Part of the strict: true tracker #10985. This fixes the 23 errors utils/plugins-helpers hits under strict mode, corrects two exported types that were wrong, and types oldVisit's visitor param.

Local fixes, no new casts

  • federation.ts
    • reduce calls get a type argument (reduce<string[]>, reduce<ReferenceSelectionSet>) instead of an untyped []/{} start value (TS2769, TS2488, TS7053).
    • getDirectivesByName accepts graphql's Maybe<FieldDefinitionNode> (TS2345).
    • graphql's optional directives/arguments get || [] fallbacks (TS18048).
    • A federation directive without a fields argument, or with a fields value that doesn't parse to a query, throws a descriptive error. It used to throw a TypeError.
  • helpers.ts
    • normalizeConfig uses reduce<Types.ConfiguredPlugin[]> (TS2322, TS2769).
    • isUsingTypes's parent param allows undefined like graphql's visitor does (TS2769).
    • An operation whose root type is missing from the schema throws a descriptive error instead of a TypeError (TS2533).
    • A null type name no longer goes to getType (TS2345).
  • utils.ts: mergeOutputs builds local prepend/append arrays instead of reusing ComplexPluginOutput, whose fields are optional (TS18048, TS2488).

Exported types corrected

  • ProfilerEvent.cat is optional. It matches Profiler.run's optional cat and the Trace Event format (TS2322). Trace files are unchanged.
  • ReferenceSelectionSet (exposed through FederationMeta) is recursive: { [field: string]: true | ReferenceSelectionSet }. It used to be Record<string, boolean>, but nested selections like company { taxCode } produce objects, not booleans. DirectiveSelectionSet.selection changes to match.

oldVisit typed

  • The visitor param is the new exported OldVisitor type: enter/leave maps keyed by AST node kind. Their callbacks get the typed node plus graphql's key, parent, path and ancestors (TS7006 on the federation visitor callbacks).
  • The callbacks are declared with method syntax, the same trick as @types/react's bivarianceHack. That way visitor class instances whose methods declare narrower param types (such as ancestors: ASTNode[]) are still accepted without casts.
  • Because of this, typescript's and typescript-operations' InputValueDefinition visitor methods take path and ancestors as required. graphql's visit always passes them.

Generated output is unchanged. The strict typecheck goes from 228 to 205 errors: 0 in plugins-helpers, and no package's count goes up.

This is the most thorough of the three options discussed. The other two were "local fixes only" and "also correct the two exported types". We picked it so the exported types and oldVisit get typed properly instead of worked around.

Related #4683

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • Ran tsc --noEmit -p tsconfig.json with the tracker's strict tsconfig.json: 0 errors under packages/utils/plugins-helpers, and no other package's count goes up.
  • pnpm types:check (the current non-strict config) is clean.
  • vitest for plugin-helpers, typescript, typescript-operations, typescript-resolvers, typescript-document-nodes, typed-document-node, visitor-plugin-common and core passes. The cli project has 72 failures about plugin/preset resolution in this environment. The same tests fail on a clean master checkout.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UcXG2hnduGFaXB3oCtb8UX


Generated by Claude Code

Fixes 20 of the 23 errors `utils/plugins-helpers` hits once the root
tsconfig enables `strict: true`, without casts:

- federation: `getDirectivesByName` accepts `null | undefined`, since
  graphql's `astNode` is `Maybe<FieldDefinitionNode>` (TS2345). `.reduce`
  calls get explicit type args instead of starting from an untyped `[]`/`{}`
  (TS2769, TS2488, TS7053). `|| []` fallbacks for graphql's optional
  `directives`/`arguments` (TS18048). A `@key`/`@requires`/`@provides`
  without a `fields` argument, or a `fields` value without a query, throws
  an explicit Error instead of the TypeError it hits today (TS18048).
- helpers: `normalizeConfig` reduces into `Types.ConfiguredPlugin[]`
  (TS2322, TS2769). `isUsingTypes`'s `SelectionSet` enter takes
  `parent` as `... | undefined` like graphql's visit fn, with a guard
  (TS2769). A missing Query/Mutation/Subscription root type throws an
  explicit Error instead of a TypeError (TS2533). `schema.getType` is only
  called with a type name (TS2345).
- utils: `mergeOutputs` collects into local `prepend`/`append` arrays, so
  the public `ComplexPluginOutput` fields stay optional (TS18048, TS2488).

Exported types changed:
- `ProfilerEvent.cat` is optional: the Trace Event format makes it
  optional and `Profiler.run(fn, name, cat?)` passes it through as-is
  (TS2322). Trace JSON is unchanged, since `JSON.stringify` drops the
  undefined `cat` as before.
- `FederationMeta`'s `ReferenceSelectionSet` is
  `{ [field: string]: true | ReferenceSelectionSet }`, which is what the
  `SelectionSet` leave visitor returns for nested selections such as
  `company { taxCode }`; `DirectiveSelectionSet.selection` matches. Its only
  consumers (visitor-plugin-common's base-resolvers-visitor and
  typescript-resolvers) read `referenceSelectionSetsString`, not the sets.

The remaining 3 errors (TS7006 on the untyped `oldVisit` callbacks in
federation) are fixed by typing `oldVisit`'s visitor.

Generated output is unchanged. The strict typecheck goes from 228 to 208 errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UcXG2hnduGFaXB3oCtb8UX

eddeee888:oss:issue-fix
`oldVisit`'s `visitor` param is typed as the exported `OldVisitor`
instead of `any`: `enter`/`leave` maps keyed by AST node kind
(graphql's `ASTKindToNode` keys), whose callbacks take
`(node: ASTKindToNode[K], key: string | number | undefined, parent: any,
path: ReadonlyArray<string | number>,
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>) => unknown`,
matching graphql's `ASTVisitFn`. This types the federation leave
callbacks' `node` param (TS7006).

The callbacks are declared with method syntax so their params are checked
bivariantly: the visitor classes passed as `{ leave: visitor }` declare
`ancestors: ASTNode[]` and a required `key: string | number`, which a
strict function type would reject.

typescript and typescript-operations: `InputValueDefinition` takes `path`
and `ancestors` (and, in operations, `key` and `parent`) as required
params, since the visitor always passes them. Optional params there
include `undefined`, which the readonly/mutable array mismatch leaves no
bivariant direction to accept under strict mode (TS2769 at the
`oldVisit(..., { leave: visitor })` calls). Operations' `path` is
`ReadonlyArray<string | number>` like graphql's.

Adds the changeset for both commits.

Generated output is unchanged. The strict typecheck goes from 208 to 205 errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UcXG2hnduGFaXB3oCtb8UX

eddeee888:oss:issue-fix
@changeset-bot

changeset-bot Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: db315f7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@graphql-codegen/plugin-helpers Patch
@graphql-codegen/typescript Patch
@graphql-codegen/typescript-operations Patch

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

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@graphql-codegen/time 7.1.1-alpha-20261001102714-db315f7d811d91a74e4b24f6d73df1b0fcb44619 npm ↗︎ unpkg ↗︎
@graphql-codegen/visitor-plugin-common 7.2.8-alpha-20261001102714-db315f7d811d91a74e4b24f6d73df1b0fcb44619 npm ↗︎ unpkg ↗︎
@graphql-codegen/typescript-operations 6.1.10-alpha-20261001102714-db315f7d811d91a74e4b24f6d73df1b0fcb44619 npm ↗︎ unpkg ↗︎
@graphql-codegen/typescript 6.1.1-alpha-20261001102714-db315f7d811d91a74e4b24f6d73df1b0fcb44619 npm ↗︎ unpkg ↗︎
@graphql-codegen/client-preset 6.2.1-alpha-20261001102714-db315f7d811d91a74e4b24f6d73df1b0fcb44619 npm ↗︎ unpkg ↗︎
@graphql-codegen/plugin-helpers 7.4.1-alpha-20261001102714-db315f7d811d91a74e4b24f6d73df1b0fcb44619 npm ↗︎ unpkg ↗︎

Comment thread packages/utils/plugins-helpers/src/helpers.ts Outdated
Comment thread packages/utils/plugins-helpers/src/profiler.ts
Comment thread packages/utils/plugins-helpers/src/utils.ts
Comment thread packages/utils/plugins-helpers/src/federation.ts Outdated
Comment thread packages/utils/plugins-helpers/src/helpers.ts
claude added 3 commits October 1, 2026 10:24
…s be inferred (#4683)

graphql's visit already types node as SelectionSetNode and parent as
ASTNode | ReadonlyArray<ASTNode> | undefined from context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UcXG2hnduGFaXB3oCtb8UX
…FederationReferencesToSchema (#4683)

base is already ReferenceSelectionSet[], so reduce(cb, {}) infers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UcXG2hnduGFaXB3oCtb8UX
Comment on lines +35 to +41
/**
* The `visitor` argument of `oldVisit`: `enter` and `leave` visitor maps keyed by AST node kind.
*/
export interface OldVisitor {
enter?: OldVisitorKindMap;
leave?: OldVisitorKindMap;
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between visit vs oldVisit? Keep it short dotpoints

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Visitor shape: visit (graphql, 17.0.2 here) groups handlers by kind first: { Field: { enter, leave } }. A top-level enter/leave is a single function that runs for every node. oldVisit takes the older "parallel" shape, where enter/leave are maps by kind: { leave: { Field() {}, Document() {} } }. graphql-js v16 dropped that shape (it was the 4th form in the v15 visit docs).
  • What oldVisit does: it reshapes { enter: { K }, leave: { K } } into { K: { enter, leave } } and calls visit (oldVisit.ts#L55-L72). It passes any other keys straight through. Traversal, replacing nodes with what leave returns, and the (node, key, parent, path, ancestors) args are all visit's own.
  • Why it's kept: every visitor class is used as oldVisit(ast, { leave: visitor }). The class's methods are named after the kinds (FieldDefinition, NamedType, …), so a class instance works directly as a by-kind leave map.
  • Types: visit is typed by graphql's ASTVisitor. oldVisit's visitor is now OldVisitor (this PR).

Generated by Claude Code

@eddeee888
eddeee888 marked this pull request as ready for review October 1, 2026 14:26
@eddeee888
eddeee888 merged commit eae0e62 into master Oct 1, 2026
22 checks passed
@eddeee888
eddeee888 deleted the claude/youthful-cerf-dz8xxc branch October 1, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants