Repository navigation
[visitor-plugin-common] fix: satisfy strict mode in selection-set-to-object, client-side-base-visitor and base-documents-visitor (#4683) - #11031
Conversation
🦋 Changeset detectedLatest commit: 37e83a2 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 |
🚀 Snapshot Release (
|
| Package | Version | Info |
|---|---|---|
@graphql-codegen/cli |
7.4.4-alpha-20261005142022-37e83a27ce324092c7e2beb909dccd1fc2123910 |
npm ↗︎ unpkg ↗︎ |
@graphql-codegen/time |
7.1.1-alpha-20261005142022-37e83a27ce324092c7e2beb909dccd1fc2123910 |
npm ↗︎ unpkg ↗︎ |
@graphql-codegen/visitor-plugin-common |
7.2.8-alpha-20261005142022-37e83a27ce324092c7e2beb909dccd1fc2123910 |
npm ↗︎ unpkg ↗︎ |
@graphql-codegen/typescript-operations |
6.1.10-alpha-20261005142022-37e83a27ce324092c7e2beb909dccd1fc2123910 |
npm ↗︎ unpkg ↗︎ |
@graphql-codegen/typescript |
6.1.1-alpha-20261005142022-37e83a27ce324092c7e2beb909dccd1fc2123910 |
npm ↗︎ unpkg ↗︎ |
@graphql-codegen/client-preset |
6.2.1-alpha-20261005142022-37e83a27ce324092c7e2beb909dccd1fc2123910 |
npm ↗︎ unpkg ↗︎ |
@graphql-codegen/plugin-helpers |
7.4.1-alpha-20261005142022-37e83a27ce324092c7e2beb909dccd1fc2123910 |
npm ↗︎ unpkg ↗︎ |
| // in case the possible type actually implements the given interface | ||
| if (isTypeSubTypeOf(this._schema, possibleType, fragmentSpreadType)) { | ||
| if ( | ||
| fragmentSpreadType && |
There was a problem hiding this comment.
Note: getType returns undefined for a fragment on a type the schema doesn't have. In that case the spread is skipped, like a type it doesn't match, instead of throwing here. Document validation normally rejects that fragment before codegen gets this far.
Generated by Claude Code
Generated by Claude Code
| spread.name.value, | ||
| fragmentSuffix, | ||
| possibleTypesForFragment.length === 1 ? null : possibleType.name, | ||
| possibleTypesForFragment.length === 1 ? undefined : possibleType.name, |
There was a problem hiding this comment.
Note: buildFragmentTypeName's third param is already optional, so passing undefined avoids widening it to also accept null.
Generated by Claude Code
Generated by Claude Code
| let typeNodes = types.get(typeName); | ||
| if (!typeNodes) { | ||
| typeNodes = []; | ||
| types.set(typeName, typeNodes); |
There was a problem hiding this comment.
Note: The list is read once into a local, so the push below doesn't need types.get(typeName) to be non-null. Calling has then get doesn't narrow the type.
Generated by Claude Code
Generated by Claude Code
| parentFieldName: parentName, | ||
| }); | ||
| prev[typeName].push(this.selectionSetStringFromFields(partialFields)); | ||
| prev[typeName].push(this.selectionSetStringFromFields(partialFields) || ''); |
There was a problem hiding this comment.
Note: selectionSetStringFromFields returns null when there are no fields. Every reader of these grouped strings filters with .filter(Boolean), or joins the union array where '' and null print the same. So the fallback is '' and GroupedStringifiedTypes doesn't have to accept null. The same applies at L663, L685, L765, L784 and L801.
Generated by Claude Code
Generated by Claude Code
| const transformed = [ | ||
| ...(transformedTypenameFields || []), | ||
| ...(transformedPrimitiveFields || []), | ||
| ...(transformedAliasesPrimitiveFields || []), | ||
| ...(transformedLinkFields || []), |
There was a problem hiding this comment.
Note: ProcessResult is null | Array<…>, and processors can return null, so || [] is real handling. The old ProcessResult annotation on this array hid that, because spreading null here would have thrown.
Generated by Claude Code
Generated by Claude Code
| meta = this._onExecutableDocumentNode({ | ||
| kind: Kind.DOCUMENT, | ||
| definitions, | ||
| definitions: definitions || [], |
There was a problem hiding this comment.
Note: definitions is an optional param of this protected method, so a subclass that doesn't pass it gets an empty document here instead of one with definitions: undefined.
Generated by Claude Code
Generated by Claude Code
| this._imports.add( | ||
| `import * as Operations from '${this.clearExtension( | ||
| this.config.importDocumentNodeExternallyFrom, | ||
| this.config.importDocumentNodeExternallyFrom || '', |
There was a problem hiding this comment.
Note: This branch is reached right after the warning above when importDocumentNodeExternallyFrom isn't set. With || '', codegen carries on past the warning and emits from '', where before extname(undefined) threw. That is a small runtime change. Same for location || '' at L678, for a document with no location.
Generated by Claude Code
Generated by Claude Code
| ...fragmentImports[0].importSource, | ||
| identifiers: unique( | ||
| flatten(fragmentImports.map(fi => fi.importSource.identifiers)), | ||
| flatten(fragmentImports.map(fi => fi.importSource.identifiers || [])), |
There was a problem hiding this comment.
Note: ImportSource.identifiers is optional, so a fragment import without identifiers adds none to the merged import instead of making flatten fail on undefined.
Generated by Claude Code
Generated by Claude Code
…object, client-side-base-visitor and base-documents-visitor (#4683) BaseDocumentsVisitor._selectionSetToObject is optional and FragmentDefinition returns `string | null`; ClientSideBaseVisitor.buildOperation returns `string | null`. LinkField's `alias` is optional, BaseSelectionSetProcessor.typeCache is keyed by `Location | undefined`, and getPossibleTypes / OperationVariablesToObject.transform accept `undefined`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Adze75p1tHBWWaHdc4fpXk eddeee888:oss:issue-fix
…hed code `transformSelectionSet` filters its results with type guards instead of `as string[]` and an annotated `.map` param. `getPossibleTypes` takes `GraphQLType | undefined`, so unwrapping lists and non-nulls needs no cast, and copies the schema's readonly possible-types array. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Adze75p1tHBWWaHdc4fpXk
| }; | ||
| export type LinkField = { | ||
| alias: string; | ||
| alias?: string; |
There was a problem hiding this comment.
Why is this optional now?
There was a problem hiding this comment.
Link fields have always been built with alias: undefined when the field has no alias (selection-set-to-object.ts L972-977: `alias: field.alias ? formatNamedField(...) : undefined`). Without `strictNullChecks` that still matched `alias: string`. With it turned on, that object fails TS2322. The processors that read `LinkField` already treat the alias as optional, using `field.alias || field.name` ([`pre-resolve-types.ts` L125](https://github.com/dotansimha/graphql-code-generator/blob/37e83a27ce324092c7e2beb909dccd1fc2123910/packages/plugins/other/visitor-plugin-common/src/selection-set-processor/pre-resolve-types.ts#L125)`` and ts-selection-set-processor.ts L85),`` so alias?: string makes the type match what the code already does.
Generated by Claude Code
Generated by Claude Code
23a96c8 to
37e83a2
Compare
| export function getPossibleTypes( | ||
| schema: GraphQLSchema, | ||
| type: GraphQLNamedType, | ||
| type: GraphQLType | undefined, |
There was a problem hiding this comment.
Why is this undefined now?
There was a problem hiding this comment.
Two kinds of callers pass undefined here:
SelectionSetToObjectpassesthis._parentSchemaType(L510,[L1132](https://github.com/dotansimha/graphql-code-generator/blob/37e83a27ce324092c7e2beb909dccd1fc2123910/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts#L1132)),`` and that is an optional constructor param (L126).``- The fragment-spread path passes the result of
schema.getType(...)(L316-317),`` which isGraphQLNamedType | undefined.
The function already handles undefined: none of the isListType/isObjectType/isAbstractType checks match it, so it falls through to return []. Widening the param removes those TS2345 errors without adding a guard at each call site. Widening it to GraphQLType rather than GraphQLNamedType also lets the recursive call pass type.ofType without the old as GraphQLNamedType cast.
Generated by Claude Code
Generated by Claude Code
|
|
||
| transform<TDefinitionType extends InterfaceOrVariable>( | ||
| variablesNode: ReadonlyArray<TDefinitionType>, | ||
| variablesNode: ReadonlyArray<TDefinitionType> | undefined, |
There was a problem hiding this comment.
Why has | undefined now?
There was a problem hiding this comment.
In graphql-js, variableDefinitions is optional on OperationDefinitionNode and FragmentDefinitionNode (readonly variableDefinitions?: ReadonlyArray<VariableDefinitionNode> | undefined, graphql-js ast.d.ts). BaseDocumentsVisitor passes node.variableDefinitions straight in (L509, [L540-542](https://github.com/dotansimha/graphql-code-generator/blob/37e83a27ce324092c7e2beb909dccd1fc2123910/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts#L540-L542)),`` which is 2 of this PR's TS2345 errors in base-documents-visitor.ts. transform already handles undefined on its first line, if (!variablesNode || variablesNode.length === 0) return null; (L53-55),`` so widening the param documents existing behaviour instead of adding || [] at every caller.
Generated by Claude Code
Generated by Claude Code
Description
This PR is part 2 of 3 for
visitor-plugin-commonin the strict-mode tracker #10985. Once the root tsconfig enablesstrict: true,selection-set-to-object.tshas 31 errors,client-side-base-visitor.tshas 17 andbase-documents-visitor.tshas 5. This PR fixes all 53. Wherenullorundefinedreally happens, it widens the shared type. Everywhere else it adds a guard, a fallback, or a throw with a clear message. It adds no newascasts or!assertions.base-types-visitor,utilsand the remaining files are left for part 3.Main
BaseDocumentsVisitor._selectionSetToObjectis optional, because onlysetSelectionSetHandlersets it.FragmentDefinitionandOperationDefinitionthrow a clear error when it isn't set.FragmentDefinitionalso throws one when the fragment's type condition isn't in the schema.BaseDocumentsVisitor.FragmentDefinitionandClientSideBaseVisitor.buildOperationreturnstring | null. Both already returnnullat runtime.SelectionSetToObject.transformSelectionSetdeclares its return type and throws when the instance has no selection set.flattenSelectionSetthrows when it has no parent type.LinkField.aliasis optional.BaseSelectionSetProcessor.typeCacheis keyed byLocation | undefined, because documents parsed withnoLocationhave noloc.getPossibleTypestakesGraphQLType | undefined, which drops itsas GraphQLNamedTypecast. It now returns a copy of the schema's readonly possible-types array instead of casting it.OperationVariablesToObject.transformacceptsundefined. Both already handled these inputs at runtime.transformSelectionSetfilters its results with type guards, which replacesas string[]and the annotated.map((t: NameAndType) => ...).buildSelectionSet, the link-field map keeps the mergedselectionSetnext to the originalfield, instead of copyingfieldwith a mergedselectionSetinto it. Alias fields take their alias from the map key.ClientSideBaseVisitordefaultsdocumentsto[]. It only calls_onExecutableDocumentNodewhen that callback is set._findDeferredFieldswalks its queue withfor...of, which also visits the selections pushed while iterating, instead ofshift().The new throws are the only runtime changes. Each one replaces a path that already failed with a TypeError.
Related #4683
Type of change
How Has This Been Tested?
master's tsconfig: 0 errorsstrict: truein root tsconfig (#4683) #10985's strict tsconfig: errors went from 127 to 73. None are left in the three files, and every other file's count is the same or lower.visitor-plugin-common,typescript,typescript-operations,typed-document-nodeandclient-preset: all pass, no snapshot changesFurther comments
selectionSetStringFromFieldsreturnsnullwhen there are no fields, for example when only__typenameis selected. The grouping in_buildGroupedSelectionsused thatnullas an object key, so it became"null". This PR keeps that key withString(...)and a comment, so generated output doesn't change.🤖 Generated with Claude Code
https://claude.ai/code/session_01Adze75p1tHBWWaHdc4fpXk
Generated by Claude Code