Skip to content

Commit 22f3b0d

Browse files
committed
when extractAllFieldsToTypesCompact is true, not conditional fragments
1 parent 497cac6 commit 22f3b0d

3 files changed

Lines changed: 220 additions & 5 deletions

File tree

packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,9 @@ export class SelectionSetToObject<
359359
break;
360360
case Kind.INLINE_FRAGMENT:
361361
if (
362-
hasConditionalDirectives(selection.directives) ||
363-
hasIncrementalDeliveryDirectives(selection.directives)
362+
!this._config.extractAllFieldsToTypesCompact &&
363+
(hasConditionalDirectives(selection.directives) ||
364+
hasIncrementalDeliveryDirectives(selection.directives))
364365
) {
365366
inlineFragmentConditionalSelections.push(selection);
366367
break;
@@ -369,8 +370,9 @@ export class SelectionSetToObject<
369370
break;
370371
case Kind.FRAGMENT_SPREAD:
371372
if (
372-
hasConditionalDirectives(selection.directives) ||
373-
hasIncrementalDeliveryDirectives(selection.directives)
373+
!this._config.extractAllFieldsToTypesCompact &&
374+
(hasConditionalDirectives(selection.directives) ||
375+
hasIncrementalDeliveryDirectives(selection.directives))
374376
) {
375377
fragmentSpreadsConditionalSelections.push(selection);
376378
break;

packages/plugins/typescript/operations/tests/extract-all-types.spec.ts

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,4 +1842,217 @@ describe('extractAllFieldsToTypesCompact: true', () => {
18421842

18431843
await validate(content);
18441844
});
1845+
1846+
it('should not separate conditional inline fragments when extractAllFieldsToTypesCompact is enabled', async () => {
1847+
// Regression: with extractAllFieldsToTypesCompact, @skip/@include inline fragments were pushed into
1848+
// selectionNodesByTypeNameConditional, causing duplicate type declarations (a syntax error).
1849+
const schema = buildSchema(/* GraphQL */ `
1850+
type Query {
1851+
user: User
1852+
}
1853+
type User {
1854+
id: ID!
1855+
name: String!
1856+
age: Int!
1857+
}
1858+
`);
1859+
1860+
const doc = parse(/* GraphQL */ `
1861+
query GetUser($showAge: Boolean!) {
1862+
user {
1863+
id
1864+
name
1865+
... on User @include(if: $showAge) {
1866+
age
1867+
}
1868+
}
1869+
}
1870+
`);
1871+
1872+
const config: TypeScriptDocumentsPluginConfig = {
1873+
extractAllFieldsToTypesCompact: true,
1874+
omitOperationSuffix: true,
1875+
};
1876+
1877+
const { content } = await plugin(
1878+
schema,
1879+
[{ location: 'test-file.ts', document: doc }],
1880+
config,
1881+
{
1882+
outputFile: '',
1883+
},
1884+
);
1885+
1886+
// Should produce valid TypeScript — no duplicate type declarations
1887+
await validate(content);
1888+
1889+
// The conditional inline fragment fields should be merged into a single flat type, not split out
1890+
expect(content).toContain('GetUser_user');
1891+
1892+
// Should NOT produce duplicate export type declarations for the same name
1893+
const duplicateRegex = /export type GetUser_user /g;
1894+
const matches = content.match(duplicateRegex);
1895+
expect(matches).toHaveLength(1);
1896+
});
1897+
1898+
it('should not separate conditional fragment spreads when extractAllFieldsToTypesCompact is enabled', async () => {
1899+
// Regression: conditional fragment spreads (@skip/@include) were pushed into
1900+
// selectionNodesByTypeNameConditional, causing missing/duplicate type declarations.
1901+
const schema = buildSchema(/* GraphQL */ `
1902+
type Query {
1903+
user: User
1904+
}
1905+
type User {
1906+
id: ID!
1907+
name: String!
1908+
age: Int!
1909+
}
1910+
`);
1911+
1912+
const doc = parse(/* GraphQL */ `
1913+
fragment UserAge on User {
1914+
age
1915+
}
1916+
1917+
query GetUser($showAge: Boolean!) {
1918+
user {
1919+
id
1920+
name
1921+
...UserAge @include(if: $showAge)
1922+
}
1923+
}
1924+
`);
1925+
1926+
const config: TypeScriptDocumentsPluginConfig = {
1927+
extractAllFieldsToTypesCompact: true,
1928+
omitOperationSuffix: true,
1929+
};
1930+
1931+
const { content } = await plugin(
1932+
schema,
1933+
[{ location: 'test-file.ts', document: doc }],
1934+
config,
1935+
{
1936+
outputFile: '',
1937+
},
1938+
);
1939+
1940+
// Should produce valid TypeScript — no duplicate type declarations
1941+
await validate(content);
1942+
1943+
// The user type should be present
1944+
expect(content).toContain('GetUser_user');
1945+
1946+
// Should NOT produce duplicate export type declarations for the same name
1947+
const duplicateRegex = /export type GetUser_user /g;
1948+
const matches = content.match(duplicateRegex);
1949+
expect(matches).toHaveLength(1);
1950+
});
1951+
1952+
it('should merge all fields into a single flat type with both conditional and unconditional selections', async () => {
1953+
const schema = buildSchema(/* GraphQL */ `
1954+
type Query {
1955+
product: Product
1956+
}
1957+
type Product {
1958+
id: ID!
1959+
title: String!
1960+
price: Float!
1961+
discount: Float
1962+
}
1963+
`);
1964+
1965+
const doc = parse(/* GraphQL */ `
1966+
query GetProduct($showPrice: Boolean!) {
1967+
product {
1968+
id
1969+
title
1970+
... on Product @include(if: $showPrice) {
1971+
price
1972+
discount
1973+
}
1974+
}
1975+
}
1976+
`);
1977+
1978+
const config: TypeScriptDocumentsPluginConfig = {
1979+
extractAllFieldsToTypesCompact: true,
1980+
omitOperationSuffix: true,
1981+
};
1982+
1983+
const { content } = await plugin(
1984+
schema,
1985+
[{ location: 'test-file.ts', document: doc }],
1986+
config,
1987+
{
1988+
outputFile: '',
1989+
},
1990+
);
1991+
1992+
await validate(content);
1993+
1994+
// All fields (including those from conditional inline fragment) should be in the same type
1995+
expect(content).toContain('id');
1996+
expect(content).toContain('title');
1997+
expect(content).toContain('price');
1998+
expect(content).toContain('discount');
1999+
2000+
// Only one type declaration for GetProduct_product
2001+
const duplicateRegex = /export type GetProduct_product /g;
2002+
expect(content.match(duplicateRegex)).toHaveLength(1);
2003+
});
2004+
2005+
it('should handle nested fragment spreads with conditional directives without missing fields', async () => {
2006+
// Regression: nested conditional fragment spreads did not propagate transitively,
2007+
// causing missing fields in generated types.
2008+
const schema = buildSchema(/* GraphQL */ `
2009+
type Query {
2010+
order: Order
2011+
}
2012+
type Order {
2013+
id: ID!
2014+
total: Float!
2015+
status: String!
2016+
}
2017+
`);
2018+
2019+
const doc = parse(/* GraphQL */ `
2020+
fragment OrderStatus on Order {
2021+
status
2022+
}
2023+
2024+
fragment OrderDetails on Order {
2025+
total
2026+
...OrderStatus @skip(if: false)
2027+
}
2028+
2029+
query GetOrder {
2030+
order {
2031+
id
2032+
...OrderDetails
2033+
}
2034+
}
2035+
`);
2036+
2037+
const config: TypeScriptDocumentsPluginConfig = {
2038+
extractAllFieldsToTypesCompact: true,
2039+
omitOperationSuffix: true,
2040+
};
2041+
2042+
const { content } = await plugin(
2043+
schema,
2044+
[{ location: 'test-file.ts', document: doc }],
2045+
config,
2046+
{
2047+
outputFile: '',
2048+
},
2049+
);
2050+
2051+
await validate(content);
2052+
2053+
// Should not produce duplicate type declarations
2054+
const duplicateRegex = /export type GetOrder_order /g;
2055+
const matches = content.match(duplicateRegex);
2056+
expect(matches).toHaveLength(1);
2057+
});
18452058
});

packages/plugins/typescript/operations/tests/ts-documents.standalone.import-types.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ describe('TypeScript Operations Plugin - Import Types with external custom Scala
435435
),
436436
]);
437437
expect(operationFileResult).toMatchInlineSnapshot(`
438-
"import type * as Types from './graphql-code-generator/path-to-other-file';
438+
"import type * as Types from './operations/path-to-other-file';
439439
440440
/** Internal type. DO NOT USE DIRECTLY. */
441441
type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };

0 commit comments

Comments
 (0)