Skip to content

Commit b917916

Browse files
committed
BREAKING CHANGE: generate marker interfaces for union types by default (#39)
1 parent 52f6b49 commit b917916

File tree

44 files changed

+408
-329
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+408
-329
lines changed

.releaserc.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ plugins:
77
- type: breaking
88
release: major
99
- type: docs
10-
release: patch
10+
release: false
1111
- type: refactor
1212
release: patch
1313
- scope: no-release
1414
release: false
15+
parserOpts:
16+
noteKeywords:
17+
- BREAKING CHANGE
18+
- BREAKING CHANGES
1519
- "@semantic-release/github"
1620
- "@semantic-release/npm"
1721
- "@semantic-release/release-notes-generator"

bun.lockb

1.29 KB
Binary file not shown.

docs/docs/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ sidebar_position: 3
55
# Configuration
66

77
```ts reference title="Config Schema"
8-
https://github.com/ExpediaGroup/graphql-kotlin-codegen/blob/main/src/config.ts#L17-L100000
8+
https://github.com/ExpediaGroup/graphql-kotlin-codegen/blob/main/src/config.ts#L1-L100000
99
```

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default [
1313
},
1414
},
1515
rules: {
16+
"no-console": "error",
1617
"@typescript-eslint/no-non-null-assertion": "error",
1718
"@typescript-eslint/no-unsafe-argument": "error",
1819
"@typescript-eslint/no-unsafe-call": "error",

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@graphql-codegen/cli": "5.0.2",
2929
"@total-typescript/ts-reset": "0.5.1",
3030
"bun-types": "1.1.4",
31-
"eslint": "9.1.0",
31+
"eslint": "9.1.1",
3232
"husky": "9.0.11",
3333
"prettier": "3.2.5",
3434
"tsup": "8.0.2",
@@ -39,7 +39,7 @@
3939
"build": "tsup src/plugin.ts --clean --dts --external graphql",
4040
"format": "prettier --write .",
4141
"format-check": "prettier --check .",
42-
"integration": "bun run build && graphql-codegen && ./gradlew compileTestKotlin",
42+
"integration": "bun run build && graphql-codegen && ./gradlew build",
4343
"lint": "eslint .",
4444
"prepack": "bun run build",
4545
"prepare": "husky",

src/config.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
array,
1616
boolean,
1717
enum_,
18+
literal,
1819
object,
1920
optional,
2021
string,
@@ -44,11 +45,11 @@ export const configSchema = object({
4445
dependentTypesInScope: optional(array(string())),
4546
/**
4647
* Denotes Kotlin classes representing union types to be treated as interfaces rather than annotation classes.
47-
* This should be used for types outside `dependentTypesInScope` that are not generated by the plugin.
48+
* @description This should be used for types outside `dependentTypesInScope` that are not generated by the plugin. Only use when unionGeneration is set to `ANNOTATION_CLASS`.
4849
*/
4950
externalUnionsAsInterfaces: optional(array(string())),
5051
/**
51-
* Additional imports to add to the generated file.
52+
* Additional imports to add to the generated file. GraphQL Kotlin annotations are always imported.
5253
* @example ["com.example.additional.import.*"]
5354
*/
5455
extraImports: optional(array(string())),
@@ -129,4 +130,12 @@ export const configSchema = object({
129130
}),
130131
),
131132
),
133+
/**
134+
* Denotes the generation strategy for union types. Defaults to `MARKER_INTERFACE`.
135+
* @description The `MARKER_INTERFACE` option is highly recommended, since it is more type-safe than using annotation classes.
136+
* @link https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/unions/
137+
*/
138+
unionGeneration: optional(
139+
union([literal("ANNOTATION_CLASS"), literal("MARKER_INTERFACE")]),
140+
),
132141
});

src/definitions/enum.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import { EnumTypeDefinitionNode, EnumValueDefinitionNode } from "graphql";
1515
import { indentMultiline } from "@graphql-codegen/visitor-plugin-common";
1616
import { buildAnnotations } from "../helpers/build-annotations";
1717
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
18-
import { CodegenConfig } from "../plugin";
18+
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";
1919

2020
export function buildEnumTypeDefinition(
2121
node: EnumTypeDefinitionNode,
22-
config: CodegenConfig,
22+
config: CodegenConfigWithDefaults,
2323
) {
2424
if (!shouldIncludeTypeDefinition(node, config)) {
2525
return "";
@@ -46,11 +46,11 @@ ${indentMultiline(enumValues.join(",\n") + ";", 2)}
4646

4747
function buildEnumValueDefinition(
4848
node: EnumValueDefinitionNode,
49-
config: CodegenConfig,
49+
config: CodegenConfigWithDefaults,
5050
) {
5151
const annotations = buildAnnotations({
5252
config,
5353
definitionNode: node,
5454
});
55-
return `${annotations}${config.convert(node)}`;
55+
return `${annotations}${config.convert?.(node)}`;
5656
}

src/definitions/input.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-defi
1616
import { buildTypeMetadata } from "../helpers/build-type-metadata";
1717
import { buildAnnotations } from "../helpers/build-annotations";
1818
import { indent } from "@graphql-codegen/visitor-plugin-common";
19-
import { CodegenConfig } from "../plugin";
19+
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";
2020

2121
export function buildInputObjectDefinition(
2222
node: InputObjectTypeDefinitionNode,
2323
schema: GraphQLSchema,
24-
config: CodegenConfig,
24+
config: CodegenConfigWithDefaults,
2525
) {
2626
if (!shouldIncludeTypeDefinition(node, config)) {
2727
return "";
@@ -47,7 +47,6 @@ export function buildInputObjectDefinition(
4747

4848
const annotations = buildAnnotations({
4949
config,
50-
inputDescription: node.description?.value,
5150
definitionNode: node,
5251
});
5352

src/definitions/interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ import { indent } from "@graphql-codegen/visitor-plugin-common";
1717
import { buildTypeMetadata } from "../helpers/build-type-metadata";
1818
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
1919
import { buildFieldDefinition } from "../helpers/build-field-definition";
20-
import { CodegenConfig } from "../plugin";
20+
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";
2121

2222
export function buildInterfaceDefinition(
2323
node: InterfaceTypeDefinitionNode,
2424
schema: GraphQLSchema,
25-
config: CodegenConfig,
25+
config: CodegenConfigWithDefaults,
2626
) {
2727
if (!shouldIncludeTypeDefinition(node, config)) {
2828
return "";

src/definitions/object.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@ import { buildAnnotations } from "../helpers/build-annotations";
1616
import { indent } from "@graphql-codegen/visitor-plugin-common";
1717
import { buildTypeMetadata } from "../helpers/build-type-metadata";
1818
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
19-
import { getDependentInterfaceNames } from "../helpers/dependent-type-utils";
19+
import {
20+
getDependentInterfaceNames,
21+
getDependentUnionsForType,
22+
} from "../helpers/dependent-type-utils";
2023
import { isResolverType } from "../helpers/is-resolver-type";
2124
import { buildFieldDefinition } from "../helpers/build-field-definition";
2225
import { isExternalField } from "../helpers/is-external-field";
23-
import { CodegenConfig } from "../plugin";
26+
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";
2427

2528
export function buildObjectTypeDefinition(
2629
node: ObjectTypeDefinitionNode,
2730
schema: GraphQLSchema,
28-
config: CodegenConfig,
31+
config: CodegenConfigWithDefaults,
2932
) {
3033
if (!shouldIncludeTypeDefinition(node, config)) {
3134
return "";
@@ -36,33 +39,38 @@ export function buildObjectTypeDefinition(
3639
definitionNode: node,
3740
});
3841
const name = node.name.value;
39-
const interfacesToInherit = getDependentInterfaceNames(node);
42+
const dependentInterfaces = getDependentInterfaceNames(node);
43+
const dependentUnions = getDependentUnionsForType(schema, node);
44+
const interfacesToInherit =
45+
config.unionGeneration === "MARKER_INTERFACE"
46+
? dependentInterfaces.concat(dependentUnions)
47+
: dependentInterfaces;
4048
const interfaceInheritance = `${interfacesToInherit.length ? ` : ${interfacesToInherit.join(", ")}` : ""}`;
4149

4250
if (isResolverType(node, config)) {
4351
return `${annotations}@GraphQLIgnore\ninterface ${name}${interfaceInheritance} {
44-
${getClassMembers({ node, schema, config })}
52+
${getDataClassMembers({ node, schema, config })}
4553
}
4654
4755
${annotations}@GraphQLIgnore\ninterface ${name}CompletableFuture {
48-
${getClassMembers({ node, schema, config, completableFuture: true })}
56+
${getDataClassMembers({ node, schema, config, completableFuture: true })}
4957
}`;
5058
}
5159

5260
return `${annotations}data class ${name}(
53-
${getClassMembers({ node, schema, config })}
61+
${getDataClassMembers({ node, schema, config })}
5462
)${interfaceInheritance}`;
5563
}
5664

57-
function getClassMembers({
65+
function getDataClassMembers({
5866
node,
5967
schema,
6068
config,
6169
completableFuture,
6270
}: {
6371
node: ObjectTypeDefinitionNode;
6472
schema: GraphQLSchema;
65-
config: CodegenConfig;
73+
config: CodegenConfigWithDefaults;
6674
completableFuture?: boolean;
6775
}) {
6876
const resolverType = isResolverType(node, config);

src/definitions/union.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,33 @@ limitations under the License.
1313

1414
import { UnionTypeDefinitionNode } from "graphql";
1515
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
16-
import { buildDirectiveAnnotations } from "../helpers/build-directive-annotations";
17-
import { CodegenConfig } from "../plugin";
18-
import { trimDescription } from "../helpers/build-annotations";
16+
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";
17+
import {
18+
buildAnnotations,
19+
trimDescription,
20+
} from "../helpers/build-annotations";
1921

2022
export function buildUnionTypeDefinition(
2123
node: UnionTypeDefinitionNode,
22-
config: CodegenConfig,
24+
config: CodegenConfigWithDefaults,
2325
) {
2426
if (!shouldIncludeTypeDefinition(node, config)) {
2527
return "";
2628
}
29+
const annotations = buildAnnotations({
30+
config,
31+
definitionNode: node,
32+
});
33+
if (config.unionGeneration === "MARKER_INTERFACE") {
34+
return `${annotations}interface ${node.name.value}`;
35+
}
2736

28-
const directiveAnnotations = buildDirectiveAnnotations(node, config);
2937
const possibleTypes =
3038
node.types?.map((type) => `${type.name.value}::class`).join(", ") || "";
31-
return `${directiveAnnotations}@GraphQLUnion(
39+
return `${annotations}@GraphQLUnion(
3240
name = "${node.name.value}",
3341
possibleTypes = [${possibleTypes}],
34-
description = "${node.description?.value ? trimDescription(node.description.value) : ""}"
42+
description = "${trimDescription(node.description?.value)}"
3543
)
3644
annotation class ${node.name.value}`;
3745
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2024 Expedia, Inc.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
https://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
import { CodegenConfigWithDefaults } from "./build-config-with-defaults";
15+
import { getDependentTypeNames } from "./get-dependent-type-names";
16+
import { GraphQLSchema } from "graphql";
17+
18+
export function addDependentTypesToOnlyTypes(
19+
config: CodegenConfigWithDefaults,
20+
schema: GraphQLSchema,
21+
) {
22+
if (!config.onlyTypes) {
23+
throw new Error(`onlyTypes config is required to add dependent types`);
24+
}
25+
const onlyTypesNodes = config.onlyTypes
26+
.map((typeName) => schema.getType(typeName)?.astNode)
27+
.filter(Boolean);
28+
const dependentTypeNames = onlyTypesNodes.flatMap((node) =>
29+
getDependentTypeNames(schema, node, config),
30+
);
31+
const typesInScope = config.dependentTypesInScope;
32+
const dependentTypesInScope = typesInScope
33+
? dependentTypeNames.filter((typeName) => typesInScope.includes(typeName))
34+
: dependentTypeNames;
35+
config.onlyTypes.push(...dependentTypesInScope);
36+
}

src/helpers/add-dependent-types.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)