@gqlkit-ts/cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto-type-generator/auto-type-generator.d.ts +26 -4
- package/dist/auto-type-generator/auto-type-generator.d.ts.map +1 -1
- package/dist/auto-type-generator/auto-type-generator.js +846 -152
- package/dist/auto-type-generator/auto-type-generator.js.map +1 -1
- package/dist/auto-type-generator/index.d.ts +9 -2
- package/dist/auto-type-generator/index.d.ts.map +1 -1
- package/dist/auto-type-generator/index.js +3 -0
- package/dist/auto-type-generator/index.js.map +1 -1
- package/dist/auto-type-generator/inline-enum-collector.d.ts +39 -0
- package/dist/auto-type-generator/inline-enum-collector.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-enum-collector.js +193 -0
- package/dist/auto-type-generator/inline-enum-collector.js.map +1 -0
- package/dist/auto-type-generator/inline-object-traverser.d.ts +20 -0
- package/dist/auto-type-generator/inline-object-traverser.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-object-traverser.js +22 -0
- package/dist/auto-type-generator/inline-object-traverser.js.map +1 -0
- package/dist/auto-type-generator/inline-union-collector.d.ts +29 -0
- package/dist/auto-type-generator/inline-union-collector.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-union-collector.js +216 -0
- package/dist/auto-type-generator/inline-union-collector.js.map +1 -0
- package/dist/auto-type-generator/inline-union-types.d.ts +29 -0
- package/dist/auto-type-generator/inline-union-types.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-union-types.js +2 -0
- package/dist/auto-type-generator/inline-union-types.js.map +1 -0
- package/dist/auto-type-generator/inline-union-validator.d.ts +76 -0
- package/dist/auto-type-generator/inline-union-validator.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-union-validator.js +329 -0
- package/dist/auto-type-generator/inline-union-validator.js.map +1 -0
- package/dist/auto-type-generator/naming-convention.d.ts +22 -1
- package/dist/auto-type-generator/naming-convention.d.ts.map +1 -1
- package/dist/auto-type-generator/naming-convention.js +24 -0
- package/dist/auto-type-generator/naming-convention.js.map +1 -1
- package/dist/auto-type-generator/resolve-type-generator.d.ts +20 -0
- package/dist/auto-type-generator/resolve-type-generator.d.ts.map +1 -0
- package/dist/auto-type-generator/resolve-type-generator.js +2 -0
- package/dist/auto-type-generator/resolve-type-generator.js.map +1 -0
- package/dist/auto-type-generator/resolver-field-iterator.d.ts +13 -0
- package/dist/auto-type-generator/resolver-field-iterator.d.ts.map +1 -0
- package/dist/auto-type-generator/resolver-field-iterator.js +22 -0
- package/dist/auto-type-generator/resolver-field-iterator.js.map +1 -0
- package/dist/auto-type-generator/typename-extractor.d.ts +26 -0
- package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -0
- package/dist/auto-type-generator/typename-extractor.js +142 -0
- package/dist/auto-type-generator/typename-extractor.js.map +1 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +35 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.js +177 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -0
- package/dist/auto-type-generator/typename-types.d.ts +43 -0
- package/dist/auto-type-generator/typename-types.d.ts.map +1 -0
- package/dist/auto-type-generator/typename-types.js +37 -0
- package/dist/auto-type-generator/typename-types.js.map +1 -0
- package/dist/auto-type-generator/typename-validator.d.ts +37 -0
- package/dist/auto-type-generator/typename-validator.d.ts.map +1 -0
- package/dist/auto-type-generator/typename-validator.js +206 -0
- package/dist/auto-type-generator/typename-validator.js.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/docs.d.ts +51 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +154 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/gen-orchestrator/orchestrator.js +119 -14
- package/dist/gen-orchestrator/orchestrator.js.map +1 -1
- package/dist/resolver-extractor/extract-resolvers.d.ts +28 -1
- package/dist/resolver-extractor/extract-resolvers.d.ts.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.d.ts +16 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.js +86 -309
- package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
- package/dist/resolver-extractor/index.d.ts +0 -1
- package/dist/resolver-extractor/index.d.ts.map +1 -1
- package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +1 -0
- package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts.map +1 -1
- package/dist/resolver-extractor/validator/abstract-resolver-validator.js +9 -5
- package/dist/resolver-extractor/validator/abstract-resolver-validator.js.map +1 -1
- package/dist/schema-generator/emitter/code-emitter.d.ts +10 -2
- package/dist/schema-generator/emitter/code-emitter.d.ts.map +1 -1
- package/dist/schema-generator/emitter/code-emitter.js +39 -4
- package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
- package/dist/schema-generator/generate-schema.d.ts +1 -0
- package/dist/schema-generator/generate-schema.d.ts.map +1 -1
- package/dist/schema-generator/generate-schema.js +90 -4
- package/dist/schema-generator/generate-schema.js.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.d.ts +34 -2
- package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.js +96 -1
- package/dist/schema-generator/integrator/result-integrator.js.map +1 -1
- package/dist/schema-generator/resolver-collector/resolver-collector.d.ts +2 -0
- package/dist/schema-generator/resolver-collector/resolver-collector.d.ts.map +1 -1
- package/dist/schema-generator/resolver-collector/resolver-collector.js +22 -0
- package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
- package/dist/shared/constants.d.ts +0 -16
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +0 -19
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/directive-detector.d.ts.map +1 -1
- package/dist/shared/directive-detector.js +8 -15
- package/dist/shared/directive-detector.js.map +1 -1
- package/dist/shared/enum-prefix-detector.d.ts +63 -0
- package/dist/shared/enum-prefix-detector.d.ts.map +1 -0
- package/dist/shared/enum-prefix-detector.js +80 -0
- package/dist/shared/enum-prefix-detector.js.map +1 -0
- package/dist/shared/file-scanner.d.ts.map +1 -1
- package/dist/shared/file-scanner.js +5 -3
- package/dist/shared/file-scanner.js.map +1 -1
- package/dist/shared/ignore-fields-detector.d.ts +26 -0
- package/dist/shared/ignore-fields-detector.d.ts.map +1 -0
- package/dist/shared/ignore-fields-detector.js +83 -0
- package/dist/shared/ignore-fields-detector.js.map +1 -0
- package/dist/shared/ignore-fields-validator.d.ts +29 -0
- package/dist/shared/ignore-fields-validator.d.ts.map +1 -0
- package/dist/shared/ignore-fields-validator.js +43 -0
- package/dist/shared/ignore-fields-validator.js.map +1 -0
- package/dist/shared/index.d.ts +3 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -3
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/interface-detector.d.ts +3 -2
- package/dist/shared/interface-detector.d.ts.map +1 -1
- package/dist/shared/interface-detector.js +54 -11
- package/dist/shared/interface-detector.js.map +1 -1
- package/dist/shared/path-utils.d.ts +2 -0
- package/dist/shared/path-utils.d.ts.map +1 -0
- package/dist/shared/path-utils.js +4 -0
- package/dist/shared/path-utils.js.map +1 -0
- package/dist/shared/source-location.d.ts +5 -0
- package/dist/shared/source-location.d.ts.map +1 -1
- package/dist/shared/source-location.js +7 -0
- package/dist/shared/source-location.js.map +1 -1
- package/dist/shared/type-converter.d.ts.map +1 -1
- package/dist/shared/type-converter.js +11 -0
- package/dist/shared/type-converter.js.map +1 -1
- package/dist/shared/typescript-utils.d.ts +34 -7
- package/dist/shared/typescript-utils.d.ts.map +1 -1
- package/dist/shared/typescript-utils.js +72 -24
- package/dist/shared/typescript-utils.js.map +1 -1
- package/dist/type-extractor/collector/scalar-collector.d.ts.map +1 -1
- package/dist/type-extractor/collector/scalar-collector.js +4 -14
- package/dist/type-extractor/collector/scalar-collector.js.map +1 -1
- package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
- package/dist/type-extractor/converter/graphql-converter.js +57 -4
- package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
- package/dist/type-extractor/extractor/field-type-resolver.d.ts +28 -0
- package/dist/type-extractor/extractor/field-type-resolver.d.ts.map +1 -0
- package/dist/type-extractor/extractor/field-type-resolver.js +433 -0
- package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -0
- package/dist/type-extractor/extractor/type-extractor.d.ts +12 -3
- package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor/extractor/type-extractor.js +260 -229
- package/dist/type-extractor/extractor/type-extractor.js.map +1 -1
- package/dist/type-extractor/extractor/type-name-collector.d.ts +24 -0
- package/dist/type-extractor/extractor/type-name-collector.d.ts.map +1 -0
- package/dist/type-extractor/extractor/type-name-collector.js +102 -0
- package/dist/type-extractor/extractor/type-name-collector.js.map +1 -0
- package/dist/type-extractor/mapper/scalar-base-type-mapper.d.ts +89 -0
- package/dist/type-extractor/mapper/scalar-base-type-mapper.d.ts.map +1 -0
- package/dist/type-extractor/mapper/scalar-base-type-mapper.js +158 -0
- package/dist/type-extractor/mapper/scalar-base-type-mapper.js.map +1 -0
- package/dist/type-extractor/types/diagnostics.d.ts +1 -1
- package/dist/type-extractor/types/diagnostics.d.ts.map +1 -1
- package/dist/type-extractor/types/graphql.d.ts +2 -0
- package/dist/type-extractor/types/graphql.d.ts.map +1 -1
- package/dist/type-extractor/types/index.d.ts +2 -1
- package/dist/type-extractor/types/index.d.ts.map +1 -1
- package/dist/type-extractor/types/index.js +1 -1
- package/dist/type-extractor/types/index.js.map +1 -1
- package/dist/type-extractor/types/ts-type-reference-factory.d.ts +47 -0
- package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -0
- package/dist/type-extractor/types/ts-type-reference-factory.js +75 -0
- package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -0
- package/dist/type-extractor/types/typescript.d.ts +25 -1
- package/dist/type-extractor/types/typescript.d.ts.map +1 -1
- package/dist/type-extractor/validator/type-validator.js +1 -1
- package/dist/type-extractor/validator/type-validator.js.map +1 -1
- package/docs/coding-agents.md +64 -0
- package/docs/configuration.md +6 -20
- package/docs/getting-started.md +15 -12
- package/docs/index.md +36 -21
- package/docs/integration/apollo.md +8 -40
- package/docs/integration/drizzle.md +187 -0
- package/docs/integration/prisma.md +196 -0
- package/docs/integration/yoga.md +8 -40
- package/docs/schema/abstract-resolvers.md +117 -0
- package/docs/schema/directives.md +5 -0
- package/docs/schema/documentation.md +5 -0
- package/docs/schema/enums.md +307 -0
- package/docs/schema/fields.md +66 -0
- package/docs/schema/index.md +21 -0
- package/docs/schema/inputs.md +117 -15
- package/docs/schema/interfaces.md +31 -1
- package/docs/schema/objects.md +42 -0
- package/docs/schema/queries-mutations.md +138 -22
- package/docs/schema/scalars.md +5 -0
- package/docs/schema/unions.md +208 -1
- package/docs/what-is-gqlkit.md +13 -8
- package/package.json +14 -5
- package/src/auto-type-generator/auto-type-generator.ts +1670 -0
- package/src/auto-type-generator/index.ts +63 -0
- package/src/auto-type-generator/inline-enum-collector.ts +338 -0
- package/src/auto-type-generator/inline-object-traverser.ts +49 -0
- package/src/auto-type-generator/inline-union-collector.ts +402 -0
- package/src/auto-type-generator/inline-union-types.ts +33 -0
- package/src/auto-type-generator/inline-union-validator.ts +482 -0
- package/src/auto-type-generator/name-collision-validator.ts +119 -0
- package/src/auto-type-generator/naming-convention.ts +163 -0
- package/src/auto-type-generator/resolve-type-generator.ts +21 -0
- package/src/auto-type-generator/resolver-field-iterator.ts +39 -0
- package/src/auto-type-generator/typename-extractor.ts +230 -0
- package/src/auto-type-generator/typename-resolve-type-generator.ts +281 -0
- package/src/auto-type-generator/typename-types.ts +66 -0
- package/src/auto-type-generator/typename-validator.ts +326 -0
- package/src/cli.ts +13 -0
- package/src/commands/docs.ts +211 -0
- package/src/commands/gen.ts +141 -0
- package/src/commands/main.ts +5 -0
- package/src/config/define-config.ts +28 -0
- package/src/config/index.ts +7 -0
- package/src/config/types.ts +144 -0
- package/src/config-loader/index.ts +14 -0
- package/src/config-loader/loader.ts +143 -0
- package/src/config-loader/validator.ts +672 -0
- package/src/gen-orchestrator/hook-executor/hook-executor.ts +117 -0
- package/src/gen-orchestrator/orchestrator.ts +798 -0
- package/src/gen-orchestrator/reporter/diagnostic-reporter.ts +44 -0
- package/src/gen-orchestrator/reporter/progress-reporter.ts +61 -0
- package/src/gen-orchestrator/writer/file-writer.ts +38 -0
- package/src/index.ts +2 -0
- package/src/resolver-extractor/extract-resolvers.ts +82 -0
- package/src/resolver-extractor/extractor/define-api-extractor.ts +740 -0
- package/src/resolver-extractor/index.ts +13 -0
- package/src/resolver-extractor/validator/abstract-resolver-validator.ts +259 -0
- package/src/schema-generator/builder/ast-builder.ts +706 -0
- package/src/schema-generator/emitter/code-emitter.ts +385 -0
- package/src/schema-generator/emitter/sdl-emitter.ts +13 -0
- package/src/schema-generator/generate-schema.ts +267 -0
- package/src/schema-generator/index.ts +19 -0
- package/src/schema-generator/integrator/result-integrator.ts +759 -0
- package/src/schema-generator/pruner/schema-pruner.ts +112 -0
- package/src/schema-generator/resolver-collector/resolver-collector.ts +157 -0
- package/src/shared/constants.ts +122 -0
- package/src/shared/default-value-detector.ts +172 -0
- package/src/shared/diagnostics.ts +35 -0
- package/src/shared/directive-definition-extractor.ts +564 -0
- package/src/shared/directive-detector.ts +556 -0
- package/src/shared/enum-prefix-detector.ts +99 -0
- package/src/shared/file-scanner.ts +170 -0
- package/src/shared/ignore-fields-detector.ts +109 -0
- package/src/shared/ignore-fields-validator.ts +66 -0
- package/src/shared/index.ts +34 -0
- package/src/shared/inline-object-extractor.ts +102 -0
- package/src/shared/inline-object-utils.ts +23 -0
- package/src/shared/interface-detector.ts +176 -0
- package/src/shared/interface-validator.ts +211 -0
- package/src/shared/metadata-detector.ts +443 -0
- package/src/shared/path-utils.ts +3 -0
- package/src/shared/program-factory.ts +51 -0
- package/src/shared/source-location.ts +38 -0
- package/src/shared/tsconfig-loader.ts +126 -0
- package/src/shared/tsdoc-parser.ts +155 -0
- package/src/shared/type-converter.ts +99 -0
- package/src/shared/typescript-utils.ts +246 -0
- package/src/type-extractor/collector/result-collector.ts +57 -0
- package/src/type-extractor/collector/scalar-collector.ts +254 -0
- package/src/type-extractor/converter/field-eligibility.ts +112 -0
- package/src/type-extractor/converter/graphql-converter.ts +483 -0
- package/src/type-extractor/extract-types.ts +1 -0
- package/src/type-extractor/extractor/field-type-resolver.ts +614 -0
- package/src/type-extractor/extractor/type-extractor.ts +1644 -0
- package/src/type-extractor/extractor/type-name-collector.ts +130 -0
- package/src/type-extractor/index.ts +20 -0
- package/src/type-extractor/mapper/scalar-base-type-mapper.ts +265 -0
- package/src/type-extractor/types/diagnostics.ts +109 -0
- package/src/type-extractor/types/graphql.ts +55 -0
- package/src/type-extractor/types/index.ts +37 -0
- package/src/type-extractor/types/ts-type-reference-factory.ts +150 -0
- package/src/type-extractor/types/typescript.ts +137 -0
- package/src/type-extractor/validator/type-validator.ts +77 -0
- package/dist/auto-type-generator/auto-type-generator.test.d.ts +0 -2
- package/dist/auto-type-generator/auto-type-generator.test.d.ts.map +0 -1
- package/dist/auto-type-generator/auto-type-generator.test.js +0 -613
- package/dist/auto-type-generator/auto-type-generator.test.js.map +0 -1
- package/dist/auto-type-generator/name-collision-validator.test.d.ts +0 -2
- package/dist/auto-type-generator/name-collision-validator.test.d.ts.map +0 -1
- package/dist/auto-type-generator/name-collision-validator.test.js +0 -358
- package/dist/auto-type-generator/name-collision-validator.test.js.map +0 -1
- package/dist/auto-type-generator/naming-convention.test.d.ts +0 -2
- package/dist/auto-type-generator/naming-convention.test.d.ts.map +0 -1
- package/dist/auto-type-generator/naming-convention.test.js +0 -132
- package/dist/auto-type-generator/naming-convention.test.js.map +0 -1
- package/dist/commands/gen.test.d.ts +0 -2
- package/dist/commands/gen.test.d.ts.map +0 -1
- package/dist/commands/gen.test.js +0 -226
- package/dist/commands/gen.test.js.map +0 -1
- package/dist/config-loader/loader.test.d.ts +0 -2
- package/dist/config-loader/loader.test.d.ts.map +0 -1
- package/dist/config-loader/loader.test.js +0 -123
- package/dist/config-loader/loader.test.js.map +0 -1
- package/dist/config-loader/validator.test.d.ts +0 -2
- package/dist/config-loader/validator.test.d.ts.map +0 -1
- package/dist/config-loader/validator.test.js +0 -846
- package/dist/config-loader/validator.test.js.map +0 -1
- package/dist/gen-orchestrator/golden.test.d.ts +0 -2
- package/dist/gen-orchestrator/golden.test.d.ts.map +0 -1
- package/dist/gen-orchestrator/golden.test.js +0 -102
- package/dist/gen-orchestrator/golden.test.js.map +0 -1
- package/dist/gen-orchestrator/hook-executor/hook-executor.test.d.ts +0 -2
- package/dist/gen-orchestrator/hook-executor/hook-executor.test.d.ts.map +0 -1
- package/dist/gen-orchestrator/hook-executor/hook-executor.test.js +0 -167
- package/dist/gen-orchestrator/hook-executor/hook-executor.test.js.map +0 -1
- package/dist/gen-orchestrator/reporter/progress-reporter.test.d.ts +0 -2
- package/dist/gen-orchestrator/reporter/progress-reporter.test.d.ts.map +0 -1
- package/dist/gen-orchestrator/reporter/progress-reporter.test.js +0 -74
- package/dist/gen-orchestrator/reporter/progress-reporter.test.js.map +0 -1
- package/dist/resolver-extractor/validator/only-validator.d.ts +0 -61
- package/dist/resolver-extractor/validator/only-validator.d.ts.map +0 -1
- package/dist/resolver-extractor/validator/only-validator.js +0 -76
- package/dist/resolver-extractor/validator/only-validator.js.map +0 -1
- package/dist/resolver-extractor/validator/only-validator.test.d.ts +0 -8
- package/dist/resolver-extractor/validator/only-validator.test.d.ts.map +0 -1
- package/dist/resolver-extractor/validator/only-validator.test.js +0 -352
- package/dist/resolver-extractor/validator/only-validator.test.js.map +0 -1
- package/dist/schema-generator/builder/ast-builder.test.d.ts +0 -2
- package/dist/schema-generator/builder/ast-builder.test.d.ts.map +0 -1
- package/dist/schema-generator/builder/ast-builder.test.js +0 -469
- package/dist/schema-generator/builder/ast-builder.test.js.map +0 -1
- package/dist/shared/file-scanner.test.d.ts +0 -2
- package/dist/shared/file-scanner.test.d.ts.map +0 -1
- package/dist/shared/file-scanner.test.js +0 -138
- package/dist/shared/file-scanner.test.js.map +0 -1
- package/dist/shared/interface-validator.test.d.ts +0 -2
- package/dist/shared/interface-validator.test.d.ts.map +0 -1
- package/dist/shared/interface-validator.test.js +0 -145
- package/dist/shared/interface-validator.test.js.map +0 -1
- package/dist/type-extractor/types/typescript.test.d.ts +0 -2
- package/dist/type-extractor/types/typescript.test.d.ts.map +0 -1
- package/dist/type-extractor/types/typescript.test.js +0 -287
- package/dist/type-extractor/types/typescript.test.js.map +0 -1
|
@@ -0,0 +1,1644 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { isInternalTypeSymbol } from "../../shared/constants.js";
|
|
4
|
+
import { detectDefaultValueMetadata } from "../../shared/default-value-detector.js";
|
|
5
|
+
import {
|
|
6
|
+
type DirectiveArgumentValue,
|
|
7
|
+
type DirectiveInfo,
|
|
8
|
+
detectDirectiveMetadata,
|
|
9
|
+
hasDirectiveMetadata,
|
|
10
|
+
unwrapDirectiveType,
|
|
11
|
+
} from "../../shared/directive-detector.js";
|
|
12
|
+
import { detectIgnoreFieldsMetadata } from "../../shared/ignore-fields-detector.js";
|
|
13
|
+
import { validateIgnoreFields } from "../../shared/ignore-fields-validator.js";
|
|
14
|
+
import { extractInlineObjectProperties as extractInlineObjectPropertiesShared } from "../../shared/inline-object-extractor.js";
|
|
15
|
+
import { isInlineObjectType } from "../../shared/inline-object-utils.js";
|
|
16
|
+
import {
|
|
17
|
+
extractImplementsFromDefineInterface,
|
|
18
|
+
extractImplementsFromGqlTypeDef,
|
|
19
|
+
isDefineInterfaceTypeAlias,
|
|
20
|
+
} from "../../shared/interface-detector.js";
|
|
21
|
+
import { detectScalarMetadata } from "../../shared/metadata-detector.js";
|
|
22
|
+
import {
|
|
23
|
+
getSourceLocationFromNode,
|
|
24
|
+
type SourceLocation,
|
|
25
|
+
} from "../../shared/source-location.js";
|
|
26
|
+
import {
|
|
27
|
+
extractTsDocFromSymbol,
|
|
28
|
+
extractTsDocInfo,
|
|
29
|
+
} from "../../shared/tsdoc-parser.js";
|
|
30
|
+
import {
|
|
31
|
+
extractPropertySymbols,
|
|
32
|
+
filterNonNullTypeNodes,
|
|
33
|
+
findEnumParentSymbol,
|
|
34
|
+
findNonNullTypeNode,
|
|
35
|
+
getNonNullableTypes,
|
|
36
|
+
getTypeNameFromNode,
|
|
37
|
+
hasUndefinedInType,
|
|
38
|
+
isAnonymousObjectType,
|
|
39
|
+
isBooleanUnion,
|
|
40
|
+
isExported,
|
|
41
|
+
isNullableUnion,
|
|
42
|
+
isNullOrUndefined,
|
|
43
|
+
shouldTreatIntersectionAsInline,
|
|
44
|
+
} from "../../shared/typescript-utils.js";
|
|
45
|
+
import type { ScalarMetadataInfo } from "../collector/scalar-collector.js";
|
|
46
|
+
import type {
|
|
47
|
+
ScalarBaseTypeMappingTable,
|
|
48
|
+
ScalarMappingContext,
|
|
49
|
+
} from "../mapper/scalar-base-type-mapper.js";
|
|
50
|
+
import {
|
|
51
|
+
createArrayType,
|
|
52
|
+
createInlineObjectType,
|
|
53
|
+
createLiteralType,
|
|
54
|
+
createPrimitiveType,
|
|
55
|
+
createReferenceType,
|
|
56
|
+
createScalarType,
|
|
57
|
+
createUnionType,
|
|
58
|
+
type Diagnostic,
|
|
59
|
+
type EnumMemberInfo,
|
|
60
|
+
type ExtractedTypeInfo,
|
|
61
|
+
type FieldDefinition,
|
|
62
|
+
type InlineObjectMember,
|
|
63
|
+
type InlineObjectProperty,
|
|
64
|
+
type TSTypeReference,
|
|
65
|
+
type TypeKind,
|
|
66
|
+
type TypeMetadata,
|
|
67
|
+
} from "../types/index.js";
|
|
68
|
+
import { resolveFieldType } from "./field-type-resolver.js";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Global type mapping configuration.
|
|
72
|
+
* Maps TypeScript type names to GraphQL scalar names when tsType.from is omitted.
|
|
73
|
+
*/
|
|
74
|
+
export interface GlobalTypeMapping {
|
|
75
|
+
/** TypeScript type name (e.g., "Date", "URL") */
|
|
76
|
+
readonly typeName: string;
|
|
77
|
+
/** GraphQL scalar name (e.g., "DateTime", "URL") */
|
|
78
|
+
readonly scalarName: string;
|
|
79
|
+
/** Usage constraint */
|
|
80
|
+
readonly only: "input" | "output" | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ExtractionOptions {
|
|
84
|
+
/** Global type mappings from config (scalars with tsType.from omitted) */
|
|
85
|
+
readonly globalTypeMappings: ReadonlyArray<GlobalTypeMapping>;
|
|
86
|
+
/** Set of type names declared in the schema (from Phase 1 collection) */
|
|
87
|
+
readonly knownTypeNames: ReadonlySet<string>;
|
|
88
|
+
/** Map of type names to their symbols (from Phase 1 collection) */
|
|
89
|
+
readonly knownTypeSymbols: ReadonlyMap<string, ts.Symbol>;
|
|
90
|
+
/** Map of underlying symbols to schema type names (for type alias resolution) */
|
|
91
|
+
readonly underlyingSymbolToTypeName: ReadonlyMap<ts.Symbol, string>;
|
|
92
|
+
/** Scalar base type mapping table for automatic base type -> scalar mapping */
|
|
93
|
+
readonly scalarMappingTable: ScalarBaseTypeMappingTable | null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ExtractionResult {
|
|
97
|
+
readonly types: ReadonlyArray<ExtractedTypeInfo>;
|
|
98
|
+
readonly diagnostics: ReadonlyArray<Diagnostic>;
|
|
99
|
+
readonly detectedScalarNames: ReadonlyArray<string>;
|
|
100
|
+
readonly detectedScalars: ReadonlyArray<ScalarMetadataInfo>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isDefaultExport(node: ts.Node, sourceFile: ts.SourceFile): boolean {
|
|
104
|
+
let hasDefaultExport = false;
|
|
105
|
+
const nodeName = (node as ts.DeclarationStatement).name?.getText(sourceFile);
|
|
106
|
+
|
|
107
|
+
ts.forEachChild(sourceFile, (child) => {
|
|
108
|
+
if (
|
|
109
|
+
ts.isExportAssignment(child) &&
|
|
110
|
+
!child.isExportEquals &&
|
|
111
|
+
ts.isIdentifier(child.expression)
|
|
112
|
+
) {
|
|
113
|
+
if (child.expression.text === nodeName) {
|
|
114
|
+
hasDefaultExport = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return hasDefaultExport;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface TypeReferenceResult {
|
|
123
|
+
readonly tsType: TSTypeReference;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Context for type declaration resolution.
|
|
128
|
+
* Used when processing type declarations (not field types).
|
|
129
|
+
*/
|
|
130
|
+
interface TypeDeclarationContext {
|
|
131
|
+
readonly checker: ts.TypeChecker;
|
|
132
|
+
readonly globalTypeMappings: ReadonlyArray<GlobalTypeMapping>;
|
|
133
|
+
readonly knownTypeNames: ReadonlySet<string>;
|
|
134
|
+
readonly visitedTypes: WeakSet<ts.Type>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Attempts to extract a type as an inline object, with cycle detection.
|
|
139
|
+
* Returns a reference type if a cycle is detected, otherwise returns an inline object.
|
|
140
|
+
*/
|
|
141
|
+
function tryExtractAsInlineObject(
|
|
142
|
+
type: ts.Type,
|
|
143
|
+
ctx: TypeDeclarationContext,
|
|
144
|
+
): TypeReferenceResult {
|
|
145
|
+
const { checker, visitedTypes } = ctx;
|
|
146
|
+
if (visitedTypes.has(type)) {
|
|
147
|
+
const typeName = type.symbol?.getName() ?? "Object";
|
|
148
|
+
return {
|
|
149
|
+
tsType: createReferenceType({
|
|
150
|
+
name: typeName === "__type" ? "Object" : typeName,
|
|
151
|
+
nullable: false,
|
|
152
|
+
}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
visitedTypes.add(type);
|
|
156
|
+
const inlineProperties = extractInlineObjectPropertiesShared(
|
|
157
|
+
type,
|
|
158
|
+
checker,
|
|
159
|
+
(t) => convertTsTypeToReference(t, ctx).tsType,
|
|
160
|
+
);
|
|
161
|
+
return {
|
|
162
|
+
tsType: createInlineObjectType({
|
|
163
|
+
properties: inlineProperties,
|
|
164
|
+
description: null,
|
|
165
|
+
deprecated: null,
|
|
166
|
+
}),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function findGlobalTypeMapping(
|
|
171
|
+
typeName: string,
|
|
172
|
+
globalTypeMappings: ReadonlyArray<GlobalTypeMapping>,
|
|
173
|
+
): GlobalTypeMapping | undefined {
|
|
174
|
+
return globalTypeMappings.find((m) => m.typeName === typeName);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function convertTsTypeToReference(
|
|
178
|
+
type: ts.Type,
|
|
179
|
+
ctx: TypeDeclarationContext,
|
|
180
|
+
typeNode?: ts.TypeNode,
|
|
181
|
+
): TypeReferenceResult {
|
|
182
|
+
const { checker, globalTypeMappings, knownTypeNames } = ctx;
|
|
183
|
+
const metadataResult = detectScalarMetadata(type, checker);
|
|
184
|
+
// Skip scalar detection if it's an array of scalars (e.g., Int[])
|
|
185
|
+
// Array types should be handled by the array handling logic below
|
|
186
|
+
if (
|
|
187
|
+
metadataResult.scalarName &&
|
|
188
|
+
!metadataResult.isPrimitive &&
|
|
189
|
+
!metadataResult.isList
|
|
190
|
+
) {
|
|
191
|
+
return {
|
|
192
|
+
tsType: createScalarType({
|
|
193
|
+
name: metadataResult.scalarName,
|
|
194
|
+
scalarInfo: {
|
|
195
|
+
scalarName: metadataResult.scalarName,
|
|
196
|
+
typeName: metadataResult.scalarName,
|
|
197
|
+
baseType: undefined,
|
|
198
|
+
isCustom: true,
|
|
199
|
+
only: metadataResult.only,
|
|
200
|
+
},
|
|
201
|
+
nullable: metadataResult.nullable,
|
|
202
|
+
}),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (isBooleanUnion(type)) {
|
|
207
|
+
const nullable = isNullableUnion(type);
|
|
208
|
+
return {
|
|
209
|
+
tsType: createPrimitiveType({ name: "boolean", nullable }),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (type.isUnion()) {
|
|
214
|
+
const nullable = isNullableUnion(type);
|
|
215
|
+
|
|
216
|
+
// Preserve type alias name for enum types (string literal unions)
|
|
217
|
+
const aliasSymbol = type.aliasSymbol;
|
|
218
|
+
if (aliasSymbol) {
|
|
219
|
+
const name = aliasSymbol.getName();
|
|
220
|
+
return {
|
|
221
|
+
tsType: createReferenceType({ name, nullable }),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const nonNullTypes = getNonNullableTypes(type);
|
|
226
|
+
|
|
227
|
+
// Check if all non-null types belong to the same enum (for numeric enums)
|
|
228
|
+
const enumParentSymbol = findEnumParentSymbol(nonNullTypes);
|
|
229
|
+
if (enumParentSymbol) {
|
|
230
|
+
return {
|
|
231
|
+
tsType: createReferenceType({
|
|
232
|
+
name: enumParentSymbol.getName(),
|
|
233
|
+
nullable,
|
|
234
|
+
}),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (nonNullTypes.length === 1) {
|
|
239
|
+
// For nullable types like User | null, extract the non-null type node
|
|
240
|
+
const nonNullTypeNode =
|
|
241
|
+
typeNode && ts.isUnionTypeNode(typeNode)
|
|
242
|
+
? findNonNullTypeNode(typeNode)
|
|
243
|
+
: undefined;
|
|
244
|
+
|
|
245
|
+
const innerResult = convertTsTypeToReference(
|
|
246
|
+
nonNullTypes[0]!,
|
|
247
|
+
ctx,
|
|
248
|
+
nonNullTypeNode,
|
|
249
|
+
);
|
|
250
|
+
return {
|
|
251
|
+
tsType: { ...innerResult.tsType, nullable },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const memberResults = nonNullTypes.map((t) =>
|
|
256
|
+
convertTsTypeToReference(t, ctx),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
tsType: createUnionType({
|
|
261
|
+
members: memberResults.map((r) => r.tsType),
|
|
262
|
+
nullable,
|
|
263
|
+
}),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (checker.isArrayType(type)) {
|
|
268
|
+
const typeArgs = (type as ts.TypeReference).typeArguments;
|
|
269
|
+
const elementType = typeArgs?.[0];
|
|
270
|
+
|
|
271
|
+
// Extract element type node from array type node (e.g., User[] -> User)
|
|
272
|
+
let elementTypeNode: ts.TypeNode | undefined;
|
|
273
|
+
if (typeNode && ts.isArrayTypeNode(typeNode)) {
|
|
274
|
+
elementTypeNode = typeNode.elementType;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const elementResult = elementType
|
|
278
|
+
? convertTsTypeToReference(elementType, ctx, elementTypeNode)
|
|
279
|
+
: {
|
|
280
|
+
tsType: createPrimitiveType({ name: "unknown", nullable: false }),
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
tsType: createArrayType(elementResult.tsType),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const typeString = checker.typeToString(type);
|
|
289
|
+
|
|
290
|
+
if (type.flags & ts.TypeFlags.String) {
|
|
291
|
+
return {
|
|
292
|
+
tsType: createPrimitiveType({ name: "string", nullable: false }),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (type.flags & ts.TypeFlags.Number) {
|
|
296
|
+
return {
|
|
297
|
+
tsType: createPrimitiveType({ name: "number", nullable: false }),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (
|
|
301
|
+
type.flags & ts.TypeFlags.Boolean ||
|
|
302
|
+
type.flags & ts.TypeFlags.BooleanLiteral
|
|
303
|
+
) {
|
|
304
|
+
return {
|
|
305
|
+
tsType: createPrimitiveType({ name: "boolean", nullable: false }),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
309
|
+
return {
|
|
310
|
+
tsType: createLiteralType(typeString.replace(/"/g, "")),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
314
|
+
return {
|
|
315
|
+
tsType: createLiteralType(typeString),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Handle intersection types that should be treated as inline objects
|
|
320
|
+
// This includes intersections with anonymous members OR intersections of
|
|
321
|
+
// named object types (interfaces) that are not exported as GraphQL types
|
|
322
|
+
if (type.isIntersection()) {
|
|
323
|
+
// If the intersection type has an alias symbol (e.g., Comment = GqlObject<...>),
|
|
324
|
+
// treat it as a named reference to avoid infinite recursion with self-referential types
|
|
325
|
+
if (type.aliasSymbol) {
|
|
326
|
+
const aliasName = type.aliasSymbol.getName();
|
|
327
|
+
return {
|
|
328
|
+
tsType: createReferenceType({ name: aliasName, nullable: false }),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const shouldTreatAsInline = shouldTreatIntersectionAsInline(type);
|
|
333
|
+
if (shouldTreatAsInline) {
|
|
334
|
+
return tryExtractAsInlineObject(type, ctx);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (isInlineObjectType(type)) {
|
|
339
|
+
// Check if typeNode references a known type (schema-defined type)
|
|
340
|
+
if (typeNode && ts.isTypeReferenceNode(typeNode)) {
|
|
341
|
+
const typeName = getTypeNameFromNode(typeNode);
|
|
342
|
+
if (typeName && knownTypeNames.has(typeName)) {
|
|
343
|
+
return {
|
|
344
|
+
tsType: createReferenceType({ name: typeName, nullable: false }),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return tryExtractAsInlineObject(type, ctx);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Check for utility types (Omit, Pick, Partial, Required, etc.)
|
|
353
|
+
// These create mapped types that should be treated as inline objects
|
|
354
|
+
if (type.flags & ts.TypeFlags.Object) {
|
|
355
|
+
const objectType = type as ts.ObjectType;
|
|
356
|
+
if (objectType.objectFlags & ts.ObjectFlags.Mapped) {
|
|
357
|
+
// Check if typeNode references a known type (schema-defined type).
|
|
358
|
+
// This handles Simplify<T> = { [K in keyof T]: T[K] } & {} pattern.
|
|
359
|
+
if (typeNode && ts.isTypeReferenceNode(typeNode)) {
|
|
360
|
+
const typeName = getTypeNameFromNode(typeNode);
|
|
361
|
+
// Only use typeNode name if it's in knownTypeNames (schema-defined type)
|
|
362
|
+
if (typeName && knownTypeNames.has(typeName)) {
|
|
363
|
+
return {
|
|
364
|
+
tsType: createReferenceType({ name: typeName, nullable: false }),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return tryExtractAsInlineObject(type, ctx);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (type.symbol) {
|
|
373
|
+
const symbolName = type.symbol.getName();
|
|
374
|
+
|
|
375
|
+
// Skip internal TypeScript symbols (see constants.ts for details)
|
|
376
|
+
if (!isInternalTypeSymbol(symbolName)) {
|
|
377
|
+
const globalMapping = findGlobalTypeMapping(
|
|
378
|
+
symbolName,
|
|
379
|
+
globalTypeMappings,
|
|
380
|
+
);
|
|
381
|
+
if (globalMapping) {
|
|
382
|
+
return {
|
|
383
|
+
tsType: createScalarType({
|
|
384
|
+
name: globalMapping.scalarName,
|
|
385
|
+
scalarInfo: {
|
|
386
|
+
scalarName: globalMapping.scalarName,
|
|
387
|
+
typeName: globalMapping.typeName,
|
|
388
|
+
baseType: undefined,
|
|
389
|
+
isCustom: true,
|
|
390
|
+
only: globalMapping.only,
|
|
391
|
+
},
|
|
392
|
+
nullable: false,
|
|
393
|
+
}),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
tsType: createReferenceType({ name: symbolName, nullable: false }),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
tsType: createReferenceType({ name: typeString, nullable: false }),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
interface FieldExtractionResult {
|
|
409
|
+
fields: FieldDefinition[];
|
|
410
|
+
diagnostics: Diagnostic[];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function collectAllFieldNames(
|
|
414
|
+
type: ts.Type,
|
|
415
|
+
checker: ts.TypeChecker,
|
|
416
|
+
): ReadonlySet<string> {
|
|
417
|
+
const properties = extractPropertySymbols(type, checker);
|
|
418
|
+
const fieldNames = new Set<string>();
|
|
419
|
+
for (const prop of properties) {
|
|
420
|
+
const propName = prop.getName();
|
|
421
|
+
if (!propName.startsWith(" $")) {
|
|
422
|
+
fieldNames.add(propName);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return fieldNames;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
interface ExtractFieldsParams {
|
|
429
|
+
readonly type: ts.Type;
|
|
430
|
+
readonly checker: ts.TypeChecker;
|
|
431
|
+
readonly globalTypeMappings: ReadonlyArray<GlobalTypeMapping>;
|
|
432
|
+
readonly knownTypeNames: ReadonlySet<string>;
|
|
433
|
+
readonly knownTypeSymbols: ReadonlyMap<string, ts.Symbol>;
|
|
434
|
+
readonly underlyingSymbolToTypeName: ReadonlyMap<ts.Symbol, string>;
|
|
435
|
+
readonly sourceFiles: ReadonlySet<string>;
|
|
436
|
+
readonly scalarMappingTable: ScalarBaseTypeMappingTable | null;
|
|
437
|
+
readonly scalarMappingContext: ScalarMappingContext;
|
|
438
|
+
readonly ignoreFields: ReadonlySet<string> | null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function extractFieldsFromType(
|
|
442
|
+
params: ExtractFieldsParams,
|
|
443
|
+
): FieldExtractionResult {
|
|
444
|
+
const {
|
|
445
|
+
type,
|
|
446
|
+
checker,
|
|
447
|
+
globalTypeMappings,
|
|
448
|
+
knownTypeNames,
|
|
449
|
+
knownTypeSymbols,
|
|
450
|
+
underlyingSymbolToTypeName,
|
|
451
|
+
sourceFiles,
|
|
452
|
+
scalarMappingTable,
|
|
453
|
+
scalarMappingContext,
|
|
454
|
+
ignoreFields,
|
|
455
|
+
} = params;
|
|
456
|
+
const fields: FieldDefinition[] = [];
|
|
457
|
+
const diagnostics: Diagnostic[] = [];
|
|
458
|
+
const properties = extractPropertySymbols(type, checker);
|
|
459
|
+
|
|
460
|
+
for (const prop of properties) {
|
|
461
|
+
const propName = prop.getName();
|
|
462
|
+
|
|
463
|
+
if (propName.startsWith(" $")) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (ignoreFields?.has(propName)) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const propType = checker.getTypeOfSymbol(prop);
|
|
472
|
+
const declarations = prop.getDeclarations();
|
|
473
|
+
const declaration = declarations?.[0];
|
|
474
|
+
|
|
475
|
+
const optional = hasUndefinedInType(propType);
|
|
476
|
+
|
|
477
|
+
const tsdocInfo = extractTsDocFromSymbol(prop, checker);
|
|
478
|
+
|
|
479
|
+
let actualPropType = propType;
|
|
480
|
+
let directives: ReadonlyArray<DirectiveInfo> | null = null;
|
|
481
|
+
let directiveNullable = false;
|
|
482
|
+
let defaultValue: DirectiveArgumentValue | null = null;
|
|
483
|
+
|
|
484
|
+
if (hasDirectiveMetadata(propType)) {
|
|
485
|
+
const directiveResult = detectDirectiveMetadata(propType, checker);
|
|
486
|
+
if (directiveResult.directives.length > 0) {
|
|
487
|
+
directives = directiveResult.directives;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Detect default value from $gqlkitFieldMeta
|
|
491
|
+
const defaultValueResult = detectDefaultValueMetadata(propType, checker);
|
|
492
|
+
if (defaultValueResult.defaultValue) {
|
|
493
|
+
defaultValue = defaultValueResult.defaultValue;
|
|
494
|
+
}
|
|
495
|
+
if (defaultValueResult.errors.length > 0) {
|
|
496
|
+
for (const error of defaultValueResult.errors) {
|
|
497
|
+
diagnostics.push({
|
|
498
|
+
code: error.code,
|
|
499
|
+
message: `Field '${propName}': ${error.message}`,
|
|
500
|
+
severity: "warning",
|
|
501
|
+
location: getSourceLocationFromNode(declaration),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Check if the original type is nullable before unwrapping
|
|
507
|
+
// TypeScript normalizes WithDirectives<T | null, [...]> to (T & Directive) | null
|
|
508
|
+
if (isNullableUnion(propType)) {
|
|
509
|
+
directiveNullable = true;
|
|
510
|
+
}
|
|
511
|
+
actualPropType = unwrapDirectiveType(propType, checker);
|
|
512
|
+
|
|
513
|
+
// Check if the unwrapped type (from $gqlkitOriginalType) is nullable
|
|
514
|
+
// This handles cases where TypeScript normalizes intersection types
|
|
515
|
+
// and loses the null from the outer union
|
|
516
|
+
if (!directiveNullable && isNullableUnion(actualPropType)) {
|
|
517
|
+
directiveNullable = true;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Get typeNode from property declaration to preserve type alias names
|
|
522
|
+
let propTypeNode: ts.TypeNode | undefined;
|
|
523
|
+
if (
|
|
524
|
+
declaration &&
|
|
525
|
+
(ts.isPropertySignature(declaration) ||
|
|
526
|
+
ts.isPropertyDeclaration(declaration))
|
|
527
|
+
) {
|
|
528
|
+
propTypeNode = declaration.type;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const resolvedType = resolveFieldType(actualPropType, propTypeNode, {
|
|
532
|
+
checker,
|
|
533
|
+
knownTypeNames,
|
|
534
|
+
knownTypeSymbols,
|
|
535
|
+
underlyingSymbolToTypeName,
|
|
536
|
+
globalTypeMappings,
|
|
537
|
+
sourceFiles,
|
|
538
|
+
scalarMappingTable,
|
|
539
|
+
scalarMappingContext,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Preserve nullability from original WithDirectives type
|
|
543
|
+
const tsType =
|
|
544
|
+
directiveNullable && !resolvedType.nullable
|
|
545
|
+
? { ...resolvedType, nullable: true }
|
|
546
|
+
: resolvedType;
|
|
547
|
+
|
|
548
|
+
fields.push({
|
|
549
|
+
name: propName,
|
|
550
|
+
tsType,
|
|
551
|
+
optional,
|
|
552
|
+
description: tsdocInfo.description ?? null,
|
|
553
|
+
deprecated: tsdocInfo.deprecated ?? null,
|
|
554
|
+
directives,
|
|
555
|
+
defaultValue,
|
|
556
|
+
sourceLocation: getSourceLocationFromNode(declaration),
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return { fields, diagnostics };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function isHeterogeneousEnum(node: ts.Node): boolean {
|
|
564
|
+
if (!ts.isEnumDeclaration(node)) return false;
|
|
565
|
+
const members = node.members;
|
|
566
|
+
if (members.length <= 1) return false;
|
|
567
|
+
|
|
568
|
+
let hasString = false;
|
|
569
|
+
let hasNumeric = false;
|
|
570
|
+
|
|
571
|
+
for (const member of members) {
|
|
572
|
+
const initializer = member.initializer;
|
|
573
|
+
if (initializer === undefined) {
|
|
574
|
+
hasNumeric = true;
|
|
575
|
+
} else if (ts.isStringLiteral(initializer)) {
|
|
576
|
+
hasString = true;
|
|
577
|
+
} else if (
|
|
578
|
+
ts.isNumericLiteral(initializer) ||
|
|
579
|
+
ts.isPrefixUnaryExpression(initializer)
|
|
580
|
+
) {
|
|
581
|
+
hasNumeric = true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (hasString && hasNumeric) return true;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function isConstEnum(node: ts.Node): boolean {
|
|
591
|
+
if (!ts.isEnumDeclaration(node)) return false;
|
|
592
|
+
const modifiers = ts.getCombinedModifierFlags(node);
|
|
593
|
+
return (modifiers & ts.ModifierFlags.Const) !== 0;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function isStringLiteralUnion(type: ts.Type): boolean {
|
|
597
|
+
if (!type.isUnion()) return false;
|
|
598
|
+
|
|
599
|
+
const nonNullTypes = getNonNullableTypes(type);
|
|
600
|
+
|
|
601
|
+
if (nonNullTypes.length === 0) return false;
|
|
602
|
+
|
|
603
|
+
return nonNullTypes.every((t) => t.flags & ts.TypeFlags.StringLiteral);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function getEnumMemberName(memberName: ts.PropertyName): string {
|
|
607
|
+
if (ts.isIdentifier(memberName) || ts.isStringLiteral(memberName)) {
|
|
608
|
+
return memberName.text;
|
|
609
|
+
}
|
|
610
|
+
return memberName.getText();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function extractEnumMembers(
|
|
614
|
+
node: ts.EnumDeclaration,
|
|
615
|
+
checker: ts.TypeChecker,
|
|
616
|
+
): ReadonlyArray<EnumMemberInfo> {
|
|
617
|
+
const members: EnumMemberInfo[] = [];
|
|
618
|
+
|
|
619
|
+
for (const member of node.members) {
|
|
620
|
+
const name = getEnumMemberName(member.name);
|
|
621
|
+
const initializer = member.initializer;
|
|
622
|
+
|
|
623
|
+
const symbol = checker.getSymbolAtLocation(member.name);
|
|
624
|
+
const tsdocInfo = symbol
|
|
625
|
+
? extractTsDocFromSymbol(symbol, checker)
|
|
626
|
+
: { description: undefined, deprecated: undefined };
|
|
627
|
+
|
|
628
|
+
if (initializer && ts.isStringLiteral(initializer)) {
|
|
629
|
+
members.push({
|
|
630
|
+
name,
|
|
631
|
+
value: initializer.text,
|
|
632
|
+
numericValue: null,
|
|
633
|
+
description: tsdocInfo.description ?? null,
|
|
634
|
+
deprecated: tsdocInfo.deprecated ?? null,
|
|
635
|
+
sourceLocation: getSourceLocationFromNode(member),
|
|
636
|
+
});
|
|
637
|
+
} else {
|
|
638
|
+
const constantValue = checker.getConstantValue(member);
|
|
639
|
+
if (typeof constantValue === "number") {
|
|
640
|
+
members.push({
|
|
641
|
+
name,
|
|
642
|
+
value: name,
|
|
643
|
+
numericValue: constantValue,
|
|
644
|
+
description: tsdocInfo.description ?? null,
|
|
645
|
+
deprecated: tsdocInfo.deprecated ?? null,
|
|
646
|
+
sourceLocation: getSourceLocationFromNode(member),
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return members;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const GRAPHQL_NAME_REGEX = /^[_A-Za-z][_0-9A-Za-z]*$/;
|
|
656
|
+
|
|
657
|
+
function isValidGraphQLName(name: string): boolean {
|
|
658
|
+
return GRAPHQL_NAME_REGEX.test(name);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function validateNumericEnumMembers(
|
|
662
|
+
members: ReadonlyArray<EnumMemberInfo>,
|
|
663
|
+
enumName: string,
|
|
664
|
+
enumLocation: SourceLocation,
|
|
665
|
+
): Diagnostic[] {
|
|
666
|
+
const diagnostics: Diagnostic[] = [];
|
|
667
|
+
|
|
668
|
+
const numericMembers = members.filter((m) => m.numericValue !== null);
|
|
669
|
+
if (numericMembers.length === 0) {
|
|
670
|
+
return diagnostics;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const valueToMembers = new Map<number, string[]>();
|
|
674
|
+
for (const member of numericMembers) {
|
|
675
|
+
const value = member.numericValue!;
|
|
676
|
+
const existing = valueToMembers.get(value) ?? [];
|
|
677
|
+
existing.push(member.name);
|
|
678
|
+
valueToMembers.set(value, existing);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
for (const [value, memberNames] of valueToMembers) {
|
|
682
|
+
if (memberNames.length > 1) {
|
|
683
|
+
diagnostics.push({
|
|
684
|
+
code: "DUPLICATE_ENUM_VALUE",
|
|
685
|
+
message: `Enum '${enumName}' has duplicate numeric value ${value} (used by ${memberNames.join(" and ")})`,
|
|
686
|
+
severity: "error",
|
|
687
|
+
location: enumLocation,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
for (const member of members) {
|
|
693
|
+
if (!isValidGraphQLName(member.name)) {
|
|
694
|
+
diagnostics.push({
|
|
695
|
+
code: "INVALID_ENUM_MEMBER_NAME",
|
|
696
|
+
message: `Enum member '${enumName}.${member.name}' is not a valid GraphQL identifier`,
|
|
697
|
+
severity: "error",
|
|
698
|
+
location: member.sourceLocation ?? enumLocation,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return diagnostics;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function extractStringLiteralUnionMembers(
|
|
707
|
+
type: ts.Type,
|
|
708
|
+
checker: ts.TypeChecker,
|
|
709
|
+
): ReadonlyArray<EnumMemberInfo> {
|
|
710
|
+
if (!type.isUnion()) return [];
|
|
711
|
+
|
|
712
|
+
const members: EnumMemberInfo[] = [];
|
|
713
|
+
|
|
714
|
+
for (const t of type.types) {
|
|
715
|
+
if (isNullOrUndefined(t)) {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (t.flags & ts.TypeFlags.StringLiteral) {
|
|
719
|
+
const value = checker.typeToString(t).replace(/^"|"$/g, "");
|
|
720
|
+
members.push({
|
|
721
|
+
name: value,
|
|
722
|
+
value: value,
|
|
723
|
+
numericValue: null,
|
|
724
|
+
description: null,
|
|
725
|
+
deprecated: null,
|
|
726
|
+
sourceLocation: null,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return members;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function determineTypeKind(
|
|
735
|
+
node: ts.Node,
|
|
736
|
+
type: ts.Type,
|
|
737
|
+
checker: ts.TypeChecker,
|
|
738
|
+
): TypeKind {
|
|
739
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
740
|
+
return "interface";
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
744
|
+
if (isDefineInterfaceTypeAlias(node, checker)) {
|
|
745
|
+
return "graphqlInterface";
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const unionKind = determineTypeKindFromUnion(type);
|
|
749
|
+
if (unionKind) {
|
|
750
|
+
return unionKind;
|
|
751
|
+
}
|
|
752
|
+
return "object";
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return "object";
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function determineTypeKindFromUnion(type: ts.Type): TypeKind | null {
|
|
759
|
+
if (!type.isUnion()) {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const nonNullTypes = getNonNullableTypes(type);
|
|
764
|
+
|
|
765
|
+
if (isStringLiteralUnion(type)) {
|
|
766
|
+
return "enum";
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const allObjectTypes = nonNullTypes.every(
|
|
770
|
+
(t) =>
|
|
771
|
+
(t.flags & ts.TypeFlags.Object) !== 0 ||
|
|
772
|
+
(t.flags & ts.TypeFlags.Intersection) !== 0 ||
|
|
773
|
+
t.symbol !== undefined,
|
|
774
|
+
);
|
|
775
|
+
if (nonNullTypes.length > 1 && allObjectTypes) {
|
|
776
|
+
return "union";
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function determineTypeKindFromType(
|
|
783
|
+
type: ts.Type,
|
|
784
|
+
originalSymbol: ts.Symbol,
|
|
785
|
+
): TypeKind {
|
|
786
|
+
const declarations = originalSymbol.getDeclarations();
|
|
787
|
+
const declaration = declarations?.[0];
|
|
788
|
+
|
|
789
|
+
if (declaration && ts.isInterfaceDeclaration(declaration)) {
|
|
790
|
+
return "interface";
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (declaration && ts.isEnumDeclaration(declaration)) {
|
|
794
|
+
return "enum";
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const unionKind = determineTypeKindFromUnion(type);
|
|
798
|
+
if (unionKind) {
|
|
799
|
+
return unionKind;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return "object";
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function isDeclarationInScannedFiles(
|
|
806
|
+
declaration: ts.Declaration,
|
|
807
|
+
scannedSourceFiles: ReadonlySet<string>,
|
|
808
|
+
): boolean {
|
|
809
|
+
const declSourceFileName = resolve(declaration.getSourceFile().fileName);
|
|
810
|
+
return Array.from(scannedSourceFiles).some(
|
|
811
|
+
(sf) => resolve(sf) === declSourceFileName,
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function createGenericTypeDiagnostic(
|
|
816
|
+
declaration: ts.Declaration,
|
|
817
|
+
exportedName: string,
|
|
818
|
+
location: SourceLocation,
|
|
819
|
+
): Diagnostic | null {
|
|
820
|
+
if (
|
|
821
|
+
(ts.isTypeAliasDeclaration(declaration) ||
|
|
822
|
+
ts.isInterfaceDeclaration(declaration)) &&
|
|
823
|
+
declaration.typeParameters &&
|
|
824
|
+
declaration.typeParameters.length > 0
|
|
825
|
+
) {
|
|
826
|
+
return {
|
|
827
|
+
code: "UNSUPPORTED_SYNTAX",
|
|
828
|
+
message: `Generic type '${exportedName}' is not supported. Consider using a concrete type instead.`,
|
|
829
|
+
severity: "warning",
|
|
830
|
+
location,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
interface ProcessReexportedSymbolParams {
|
|
837
|
+
readonly exportedName: string;
|
|
838
|
+
readonly resolvedSymbol: ts.Symbol;
|
|
839
|
+
readonly type: ts.Type;
|
|
840
|
+
readonly location: SourceLocation;
|
|
841
|
+
readonly filePath: string;
|
|
842
|
+
readonly checker: ts.TypeChecker;
|
|
843
|
+
readonly globalTypeMappings: ReadonlyArray<GlobalTypeMapping>;
|
|
844
|
+
readonly knownTypeNames: ReadonlySet<string>;
|
|
845
|
+
readonly knownTypeSymbols: ReadonlyMap<string, ts.Symbol>;
|
|
846
|
+
readonly underlyingSymbolToTypeName: ReadonlyMap<ts.Symbol, string>;
|
|
847
|
+
readonly scannedSourceFiles: ReadonlySet<string>;
|
|
848
|
+
readonly scalarMappingTable: ScalarBaseTypeMappingTable | null;
|
|
849
|
+
readonly scalarMappingContext: ScalarMappingContext;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
interface ProcessReexportedSymbolResult {
|
|
853
|
+
readonly typeInfo: ExtractedTypeInfo | null;
|
|
854
|
+
readonly diagnostics: Diagnostic[];
|
|
855
|
+
readonly scalarName: string | null;
|
|
856
|
+
readonly scalarMetadata: ScalarMetadataInfo | null;
|
|
857
|
+
readonly skip: boolean;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function processReexportedSymbol(
|
|
861
|
+
params: ProcessReexportedSymbolParams,
|
|
862
|
+
): ProcessReexportedSymbolResult {
|
|
863
|
+
const {
|
|
864
|
+
exportedName,
|
|
865
|
+
resolvedSymbol,
|
|
866
|
+
type,
|
|
867
|
+
location,
|
|
868
|
+
filePath,
|
|
869
|
+
checker,
|
|
870
|
+
globalTypeMappings,
|
|
871
|
+
knownTypeNames,
|
|
872
|
+
knownTypeSymbols,
|
|
873
|
+
underlyingSymbolToTypeName,
|
|
874
|
+
scannedSourceFiles,
|
|
875
|
+
scalarMappingTable,
|
|
876
|
+
scalarMappingContext,
|
|
877
|
+
} = params;
|
|
878
|
+
|
|
879
|
+
const diagnostics: Diagnostic[] = [];
|
|
880
|
+
|
|
881
|
+
const scalarMetadataResult = detectScalarMetadata(type, checker);
|
|
882
|
+
if (scalarMetadataResult.scalarName && !scalarMetadataResult.isPrimitive) {
|
|
883
|
+
const tsdocInfo = extractTsDocFromSymbol(resolvedSymbol, checker);
|
|
884
|
+
return {
|
|
885
|
+
typeInfo: null,
|
|
886
|
+
diagnostics: [],
|
|
887
|
+
scalarName: scalarMetadataResult.scalarName,
|
|
888
|
+
scalarMetadata: {
|
|
889
|
+
scalarName: scalarMetadataResult.scalarName,
|
|
890
|
+
typeName: exportedName,
|
|
891
|
+
only: scalarMetadataResult.only,
|
|
892
|
+
sourceFile: filePath,
|
|
893
|
+
line: location.line,
|
|
894
|
+
description: tsdocInfo.description ?? null,
|
|
895
|
+
},
|
|
896
|
+
skip: false,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const declarations = resolvedSymbol.getDeclarations();
|
|
901
|
+
const declaration = declarations?.[0];
|
|
902
|
+
if (declaration) {
|
|
903
|
+
if (
|
|
904
|
+
isDeclarationInScannedFiles(declaration, scannedSourceFiles) &&
|
|
905
|
+
(ts.isTypeAliasDeclaration(declaration) ||
|
|
906
|
+
ts.isInterfaceDeclaration(declaration) ||
|
|
907
|
+
ts.isEnumDeclaration(declaration))
|
|
908
|
+
) {
|
|
909
|
+
return {
|
|
910
|
+
typeInfo: null,
|
|
911
|
+
diagnostics: [],
|
|
912
|
+
scalarName: null,
|
|
913
|
+
scalarMetadata: null,
|
|
914
|
+
skip: true,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const genericDiagnostic = createGenericTypeDiagnostic(
|
|
919
|
+
declaration,
|
|
920
|
+
exportedName,
|
|
921
|
+
location,
|
|
922
|
+
);
|
|
923
|
+
if (genericDiagnostic) {
|
|
924
|
+
diagnostics.push(genericDiagnostic);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const kind = determineTypeKindFromType(type, resolvedSymbol);
|
|
929
|
+
const tsdocInfo = extractTsDocFromSymbol(resolvedSymbol, checker);
|
|
930
|
+
|
|
931
|
+
const metadata: TypeMetadata = {
|
|
932
|
+
name: exportedName,
|
|
933
|
+
kind,
|
|
934
|
+
sourceFile: filePath,
|
|
935
|
+
sourceLocation: location,
|
|
936
|
+
exportKind: "named",
|
|
937
|
+
description: tsdocInfo.description ?? null,
|
|
938
|
+
deprecated: tsdocInfo.deprecated ?? null,
|
|
939
|
+
directives: null,
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
if (kind === "enum") {
|
|
943
|
+
const declarations = resolvedSymbol.getDeclarations();
|
|
944
|
+
const declaration = declarations?.[0];
|
|
945
|
+
let enumMembers: ReadonlyArray<EnumMemberInfo>;
|
|
946
|
+
if (declaration && ts.isEnumDeclaration(declaration)) {
|
|
947
|
+
enumMembers = extractEnumMembers(declaration, checker);
|
|
948
|
+
} else {
|
|
949
|
+
enumMembers = extractStringLiteralUnionMembers(type, checker);
|
|
950
|
+
}
|
|
951
|
+
return {
|
|
952
|
+
typeInfo: {
|
|
953
|
+
metadata,
|
|
954
|
+
fields: [],
|
|
955
|
+
unionMembers: null,
|
|
956
|
+
inlineObjectMembers: null,
|
|
957
|
+
enumMembers,
|
|
958
|
+
implementedInterfaces: null,
|
|
959
|
+
},
|
|
960
|
+
diagnostics,
|
|
961
|
+
scalarName: null,
|
|
962
|
+
scalarMetadata: null,
|
|
963
|
+
skip: false,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Get typeNode for union member extraction from declaration
|
|
968
|
+
const reexportDeclarations = resolvedSymbol.getDeclarations();
|
|
969
|
+
const reexportDeclaration = reexportDeclarations?.[0];
|
|
970
|
+
const reexportTypeNode =
|
|
971
|
+
reexportDeclaration && ts.isTypeAliasDeclaration(reexportDeclaration)
|
|
972
|
+
? reexportDeclaration.type
|
|
973
|
+
: undefined;
|
|
974
|
+
const unionMembers = extractUnionMembers(type, reexportTypeNode);
|
|
975
|
+
const ignoreFields = detectIgnoreFieldsMetadata({ type, checker });
|
|
976
|
+
|
|
977
|
+
if (ignoreFields !== null && kind !== "union") {
|
|
978
|
+
const allFieldNames = collectAllFieldNames(type, checker);
|
|
979
|
+
const validationDiagnostics = validateIgnoreFields({
|
|
980
|
+
typeName: exportedName,
|
|
981
|
+
ignoreFields,
|
|
982
|
+
allFieldNames,
|
|
983
|
+
sourceLocation: location,
|
|
984
|
+
});
|
|
985
|
+
diagnostics.push(...validationDiagnostics);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const fieldResult =
|
|
989
|
+
kind === "union"
|
|
990
|
+
? { fields: [], diagnostics: [] }
|
|
991
|
+
: extractFieldsFromType({
|
|
992
|
+
type,
|
|
993
|
+
checker,
|
|
994
|
+
globalTypeMappings,
|
|
995
|
+
knownTypeNames,
|
|
996
|
+
knownTypeSymbols,
|
|
997
|
+
underlyingSymbolToTypeName,
|
|
998
|
+
sourceFiles: scannedSourceFiles,
|
|
999
|
+
scalarMappingTable,
|
|
1000
|
+
scalarMappingContext,
|
|
1001
|
+
ignoreFields,
|
|
1002
|
+
});
|
|
1003
|
+
diagnostics.push(...fieldResult.diagnostics);
|
|
1004
|
+
|
|
1005
|
+
return {
|
|
1006
|
+
typeInfo: {
|
|
1007
|
+
metadata,
|
|
1008
|
+
fields: fieldResult.fields,
|
|
1009
|
+
unionMembers: unionMembers ?? null,
|
|
1010
|
+
inlineObjectMembers: null,
|
|
1011
|
+
enumMembers: null,
|
|
1012
|
+
implementedInterfaces: null,
|
|
1013
|
+
},
|
|
1014
|
+
diagnostics,
|
|
1015
|
+
scalarName: null,
|
|
1016
|
+
scalarMetadata: null,
|
|
1017
|
+
skip: false,
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
interface ProcessExportDeclarationResult {
|
|
1022
|
+
readonly types: ExtractedTypeInfo[];
|
|
1023
|
+
readonly diagnostics: Diagnostic[];
|
|
1024
|
+
readonly detectedScalarNames: string[];
|
|
1025
|
+
readonly detectedScalars: ScalarMetadataInfo[];
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function processExportDeclaration(
|
|
1029
|
+
node: ts.ExportDeclaration,
|
|
1030
|
+
sourceFile: ts.SourceFile,
|
|
1031
|
+
filePath: string,
|
|
1032
|
+
checker: ts.TypeChecker,
|
|
1033
|
+
globalTypeMappings: ReadonlyArray<GlobalTypeMapping>,
|
|
1034
|
+
knownTypeNames: ReadonlySet<string>,
|
|
1035
|
+
knownTypeSymbols: ReadonlyMap<string, ts.Symbol>,
|
|
1036
|
+
underlyingSymbolToTypeName: ReadonlyMap<ts.Symbol, string>,
|
|
1037
|
+
scannedSourceFiles: ReadonlySet<string>,
|
|
1038
|
+
scalarMappingTable: ScalarBaseTypeMappingTable | null,
|
|
1039
|
+
): ProcessExportDeclarationResult {
|
|
1040
|
+
const types: ExtractedTypeInfo[] = [];
|
|
1041
|
+
const diagnostics: Diagnostic[] = [];
|
|
1042
|
+
const detectedScalarNames: string[] = [];
|
|
1043
|
+
const detectedScalars: ScalarMetadataInfo[] = [];
|
|
1044
|
+
|
|
1045
|
+
if (!node.isTypeOnly) {
|
|
1046
|
+
return { types, diagnostics, detectedScalarNames, detectedScalars };
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const exportClause = node.exportClause;
|
|
1050
|
+
|
|
1051
|
+
const symbolsToProcess: Array<{
|
|
1052
|
+
exportedName: string;
|
|
1053
|
+
resolvedSymbol: ts.Symbol;
|
|
1054
|
+
type: ts.Type;
|
|
1055
|
+
}> = [];
|
|
1056
|
+
|
|
1057
|
+
if (exportClause && ts.isNamedExports(exportClause)) {
|
|
1058
|
+
for (const specifier of exportClause.elements) {
|
|
1059
|
+
const exportedName = specifier.name.text;
|
|
1060
|
+
const localTargetSymbol =
|
|
1061
|
+
checker.getExportSpecifierLocalTargetSymbol(specifier);
|
|
1062
|
+
if (!localTargetSymbol) continue;
|
|
1063
|
+
|
|
1064
|
+
const originalSymbol =
|
|
1065
|
+
localTargetSymbol.flags & ts.SymbolFlags.Alias
|
|
1066
|
+
? checker.getAliasedSymbol(localTargetSymbol)
|
|
1067
|
+
: localTargetSymbol;
|
|
1068
|
+
if (!originalSymbol) continue;
|
|
1069
|
+
|
|
1070
|
+
const type = checker.getDeclaredTypeOfSymbol(originalSymbol);
|
|
1071
|
+
symbolsToProcess.push({
|
|
1072
|
+
exportedName,
|
|
1073
|
+
resolvedSymbol: originalSymbol,
|
|
1074
|
+
type,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
} else if (!exportClause && node.moduleSpecifier) {
|
|
1078
|
+
const moduleSymbol = checker.getSymbolAtLocation(node.moduleSpecifier);
|
|
1079
|
+
if (!moduleSymbol) {
|
|
1080
|
+
const location = getSourceLocationFromNode(node)!;
|
|
1081
|
+
const modulePath = ts.isStringLiteral(node.moduleSpecifier)
|
|
1082
|
+
? node.moduleSpecifier.text
|
|
1083
|
+
: node.moduleSpecifier.getText(sourceFile);
|
|
1084
|
+
diagnostics.push({
|
|
1085
|
+
code: "MODULE_RESOLUTION_ERROR",
|
|
1086
|
+
message: `Could not resolve module '${modulePath}'`,
|
|
1087
|
+
severity: "error",
|
|
1088
|
+
location,
|
|
1089
|
+
});
|
|
1090
|
+
return { types, diagnostics, detectedScalarNames, detectedScalars };
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const exports = checker.getExportsOfModule(moduleSymbol);
|
|
1094
|
+
for (const exportedSymbol of exports) {
|
|
1095
|
+
const resolvedSymbol =
|
|
1096
|
+
exportedSymbol.flags & ts.SymbolFlags.Alias
|
|
1097
|
+
? checker.getAliasedSymbol(exportedSymbol)
|
|
1098
|
+
: exportedSymbol;
|
|
1099
|
+
|
|
1100
|
+
if (
|
|
1101
|
+
!(
|
|
1102
|
+
resolvedSymbol.flags & ts.SymbolFlags.TypeAlias ||
|
|
1103
|
+
resolvedSymbol.flags & ts.SymbolFlags.Interface ||
|
|
1104
|
+
resolvedSymbol.flags & ts.SymbolFlags.Enum
|
|
1105
|
+
)
|
|
1106
|
+
) {
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const type = checker.getDeclaredTypeOfSymbol(resolvedSymbol);
|
|
1111
|
+
symbolsToProcess.push({
|
|
1112
|
+
exportedName: exportedSymbol.getName(),
|
|
1113
|
+
resolvedSymbol,
|
|
1114
|
+
type,
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const location = getSourceLocationFromNode(node)!;
|
|
1120
|
+
for (const { exportedName, resolvedSymbol, type } of symbolsToProcess) {
|
|
1121
|
+
const result = processReexportedSymbol({
|
|
1122
|
+
exportedName,
|
|
1123
|
+
resolvedSymbol,
|
|
1124
|
+
type,
|
|
1125
|
+
location,
|
|
1126
|
+
filePath,
|
|
1127
|
+
checker,
|
|
1128
|
+
globalTypeMappings,
|
|
1129
|
+
knownTypeNames,
|
|
1130
|
+
knownTypeSymbols,
|
|
1131
|
+
underlyingSymbolToTypeName,
|
|
1132
|
+
scannedSourceFiles,
|
|
1133
|
+
scalarMappingTable,
|
|
1134
|
+
scalarMappingContext: exportedName.endsWith("Input") ? "input" : "output",
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
if (result.skip) continue;
|
|
1138
|
+
|
|
1139
|
+
if (result.scalarName && result.scalarMetadata) {
|
|
1140
|
+
detectedScalarNames.push(result.scalarName);
|
|
1141
|
+
detectedScalars.push(result.scalarMetadata);
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
diagnostics.push(...result.diagnostics);
|
|
1146
|
+
if (result.typeInfo) {
|
|
1147
|
+
types.push(result.typeInfo);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return { types, diagnostics, detectedScalarNames, detectedScalars };
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function getNamedTypeName(memberType: ts.Type): string {
|
|
1155
|
+
// For type aliases (e.g., GqlObject<...>), use aliasSymbol
|
|
1156
|
+
if (memberType.aliasSymbol) {
|
|
1157
|
+
return memberType.aliasSymbol.getName();
|
|
1158
|
+
}
|
|
1159
|
+
// For regular types, use symbol
|
|
1160
|
+
return memberType.symbol?.getName() ?? "";
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
interface InlineObjectExtractionResult {
|
|
1164
|
+
readonly members: InlineObjectMember[];
|
|
1165
|
+
readonly hasInlineObjects: boolean;
|
|
1166
|
+
readonly hasNamedTypes: boolean;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
interface ExtractInlineObjectMembersParams {
|
|
1170
|
+
readonly type: ts.Type;
|
|
1171
|
+
readonly checker: ts.TypeChecker;
|
|
1172
|
+
readonly globalTypeMappings: ReadonlyArray<GlobalTypeMapping>;
|
|
1173
|
+
readonly knownTypeNames: ReadonlySet<string>;
|
|
1174
|
+
readonly typeNode: ts.TypeNode | undefined;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function extractInlineObjectMembers(
|
|
1178
|
+
params: ExtractInlineObjectMembersParams,
|
|
1179
|
+
): InlineObjectExtractionResult | null {
|
|
1180
|
+
const { type, checker, globalTypeMappings, knownTypeNames, typeNode } =
|
|
1181
|
+
params;
|
|
1182
|
+
if (!type.isUnion()) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const nonNullTypes = getNonNullableTypes(type);
|
|
1187
|
+
const memberTypeNodes =
|
|
1188
|
+
typeNode && ts.isUnionTypeNode(typeNode)
|
|
1189
|
+
? filterNonNullTypeNodes(typeNode)
|
|
1190
|
+
: [];
|
|
1191
|
+
|
|
1192
|
+
const allObjectTypes = nonNullTypes.every(
|
|
1193
|
+
(t) =>
|
|
1194
|
+
(t.flags & ts.TypeFlags.Object) !== 0 ||
|
|
1195
|
+
(t.flags & ts.TypeFlags.Intersection) !== 0,
|
|
1196
|
+
);
|
|
1197
|
+
|
|
1198
|
+
if (nonNullTypes.length < 2 || !allObjectTypes) {
|
|
1199
|
+
return null;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
let hasInlineObjects = false;
|
|
1203
|
+
let hasNamedTypes = false;
|
|
1204
|
+
const members: InlineObjectMember[] = [];
|
|
1205
|
+
|
|
1206
|
+
const ctx: TypeDeclarationContext = {
|
|
1207
|
+
checker,
|
|
1208
|
+
globalTypeMappings,
|
|
1209
|
+
knownTypeNames,
|
|
1210
|
+
visitedTypes: new WeakSet(),
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
if (memberTypeNodes.length > 0) {
|
|
1214
|
+
for (const memberNode of memberTypeNodes) {
|
|
1215
|
+
if (ts.isTypeReferenceNode(memberNode)) {
|
|
1216
|
+
hasNamedTypes = true;
|
|
1217
|
+
} else {
|
|
1218
|
+
hasInlineObjects = true;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
} else {
|
|
1222
|
+
for (const memberType of nonNullTypes) {
|
|
1223
|
+
if (isAnonymousObjectType(memberType)) {
|
|
1224
|
+
hasInlineObjects = true;
|
|
1225
|
+
} else {
|
|
1226
|
+
hasNamedTypes = true;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (hasInlineObjects) {
|
|
1232
|
+
for (const memberType of nonNullTypes) {
|
|
1233
|
+
if (isAnonymousObjectType(memberType)) {
|
|
1234
|
+
const properties = memberType.getProperties();
|
|
1235
|
+
const memberProperties: InlineObjectProperty[] = [];
|
|
1236
|
+
|
|
1237
|
+
for (const prop of properties) {
|
|
1238
|
+
const propType = checker.getTypeOfSymbol(prop);
|
|
1239
|
+
const tsdocInfo = extractTsDocFromSymbol(prop, checker);
|
|
1240
|
+
const typeResult = convertTsTypeToReference(propType, ctx);
|
|
1241
|
+
|
|
1242
|
+
memberProperties.push({
|
|
1243
|
+
propertyName: prop.getName(),
|
|
1244
|
+
propertyType: typeResult.tsType,
|
|
1245
|
+
description: tsdocInfo.description ?? null,
|
|
1246
|
+
deprecated: tsdocInfo.deprecated ?? null,
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
members.push({ properties: memberProperties });
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
return { members, hasInlineObjects, hasNamedTypes };
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function extractUnionMembers(
|
|
1259
|
+
type: ts.Type,
|
|
1260
|
+
typeNode?: ts.TypeNode,
|
|
1261
|
+
): string[] | undefined {
|
|
1262
|
+
if (!type.isUnion()) {
|
|
1263
|
+
return undefined;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const nonNullTypes = getNonNullableTypes(type);
|
|
1267
|
+
|
|
1268
|
+
const allObjectTypes = nonNullTypes.every(
|
|
1269
|
+
(t) =>
|
|
1270
|
+
(t.flags & ts.TypeFlags.Object) !== 0 ||
|
|
1271
|
+
(t.flags & ts.TypeFlags.Intersection) !== 0 ||
|
|
1272
|
+
t.symbol !== undefined,
|
|
1273
|
+
);
|
|
1274
|
+
|
|
1275
|
+
if (nonNullTypes.length > 1 && allObjectTypes) {
|
|
1276
|
+
// Extract member type nodes from union type node if available
|
|
1277
|
+
const memberTypeNodes =
|
|
1278
|
+
typeNode && ts.isUnionTypeNode(typeNode)
|
|
1279
|
+
? filterNonNullTypeNodes(typeNode)
|
|
1280
|
+
: [];
|
|
1281
|
+
|
|
1282
|
+
const namedMembers = nonNullTypes
|
|
1283
|
+
.map((t, index) => {
|
|
1284
|
+
// First try to get name from type
|
|
1285
|
+
if (!isAnonymousObjectType(t)) {
|
|
1286
|
+
const name = getNamedTypeName(t);
|
|
1287
|
+
if (name !== "" && name !== "__type") {
|
|
1288
|
+
return name;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
// Fallback to typeNode name for Simplify<T> pattern
|
|
1292
|
+
if (memberTypeNodes[index]) {
|
|
1293
|
+
const memberNode = memberTypeNodes[index];
|
|
1294
|
+
if (ts.isTypeReferenceNode(memberNode)) {
|
|
1295
|
+
return getTypeNameFromNode(memberNode) ?? "";
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return "";
|
|
1299
|
+
})
|
|
1300
|
+
.filter((name) => name !== "" && name !== "__type");
|
|
1301
|
+
|
|
1302
|
+
if (namedMembers.length > 0) {
|
|
1303
|
+
return namedMembers.sort();
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
return undefined;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
export function extractTypesFromProgram(
|
|
1311
|
+
program: ts.Program,
|
|
1312
|
+
sourceFiles: ReadonlyArray<string>,
|
|
1313
|
+
options: ExtractionOptions,
|
|
1314
|
+
): ExtractionResult {
|
|
1315
|
+
const checker = program.getTypeChecker();
|
|
1316
|
+
const types: ExtractedTypeInfo[] = [];
|
|
1317
|
+
const diagnostics: Diagnostic[] = [];
|
|
1318
|
+
const detectedScalarNames = new Set<string>();
|
|
1319
|
+
const detectedScalars: ScalarMetadataInfo[] = [];
|
|
1320
|
+
const {
|
|
1321
|
+
globalTypeMappings,
|
|
1322
|
+
knownTypeNames,
|
|
1323
|
+
knownTypeSymbols,
|
|
1324
|
+
underlyingSymbolToTypeName,
|
|
1325
|
+
scalarMappingTable,
|
|
1326
|
+
} = options;
|
|
1327
|
+
const scannedSourceFilesSet = new Set(sourceFiles);
|
|
1328
|
+
|
|
1329
|
+
for (const filePath of sourceFiles) {
|
|
1330
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
1331
|
+
if (!sourceFile) {
|
|
1332
|
+
diagnostics.push({
|
|
1333
|
+
code: "PARSE_ERROR",
|
|
1334
|
+
message: `Could not load source file: ${filePath}`,
|
|
1335
|
+
severity: "error",
|
|
1336
|
+
location: { file: filePath, line: 1, column: 1 },
|
|
1337
|
+
});
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
1342
|
+
if (ts.isEnumDeclaration(node)) {
|
|
1343
|
+
const hasExport = isExported(node);
|
|
1344
|
+
const hasDefaultExport = isDefaultExport(node, sourceFile);
|
|
1345
|
+
|
|
1346
|
+
if (!hasExport && !hasDefaultExport) {
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const name = node.name.getText(sourceFile);
|
|
1351
|
+
const location = getSourceLocationFromNode(node)!;
|
|
1352
|
+
|
|
1353
|
+
if (isConstEnum(node)) {
|
|
1354
|
+
diagnostics.push({
|
|
1355
|
+
code: "UNSUPPORTED_ENUM_TYPE",
|
|
1356
|
+
message: `Const enum '${name}' is not supported. Use a regular enum instead.`,
|
|
1357
|
+
severity: "error",
|
|
1358
|
+
location,
|
|
1359
|
+
});
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if (isHeterogeneousEnum(node)) {
|
|
1364
|
+
diagnostics.push({
|
|
1365
|
+
code: "UNSUPPORTED_ENUM_TYPE",
|
|
1366
|
+
message: `Heterogeneous enum '${name}' is not supported. Use a string enum instead.`,
|
|
1367
|
+
severity: "error",
|
|
1368
|
+
location,
|
|
1369
|
+
});
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const enumMembers = extractEnumMembers(node, checker);
|
|
1374
|
+
|
|
1375
|
+
const validationDiagnostics = validateNumericEnumMembers(
|
|
1376
|
+
enumMembers,
|
|
1377
|
+
name,
|
|
1378
|
+
location,
|
|
1379
|
+
);
|
|
1380
|
+
if (validationDiagnostics.length > 0) {
|
|
1381
|
+
diagnostics.push(...validationDiagnostics);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const tsdocInfo = extractTsDocInfo(node, checker);
|
|
1386
|
+
const metadata: TypeMetadata = {
|
|
1387
|
+
name,
|
|
1388
|
+
kind: "enum",
|
|
1389
|
+
sourceFile: filePath,
|
|
1390
|
+
sourceLocation: location,
|
|
1391
|
+
exportKind: hasDefaultExport ? "default" : "named",
|
|
1392
|
+
description: tsdocInfo.description,
|
|
1393
|
+
deprecated: tsdocInfo.deprecated,
|
|
1394
|
+
directives: null,
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
types.push({
|
|
1398
|
+
metadata,
|
|
1399
|
+
fields: [],
|
|
1400
|
+
unionMembers: null,
|
|
1401
|
+
inlineObjectMembers: null,
|
|
1402
|
+
enumMembers,
|
|
1403
|
+
implementedInterfaces: null,
|
|
1404
|
+
});
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
1409
|
+
const hasExport = isExported(node);
|
|
1410
|
+
const hasDefaultExport = isDefaultExport(node, sourceFile);
|
|
1411
|
+
|
|
1412
|
+
if (!hasExport && !hasDefaultExport) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const name = node.name.getText(sourceFile);
|
|
1417
|
+
const typeSourceLocation = getSourceLocationFromNode(node)!;
|
|
1418
|
+
|
|
1419
|
+
if (node.typeParameters && node.typeParameters.length > 0) {
|
|
1420
|
+
diagnostics.push({
|
|
1421
|
+
code: "UNSUPPORTED_SYNTAX",
|
|
1422
|
+
message: `Generic type '${name}' is not supported. Consider using a concrete type instead.`,
|
|
1423
|
+
severity: "warning",
|
|
1424
|
+
location: typeSourceLocation,
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const symbol = checker.getSymbolAtLocation(node.name);
|
|
1429
|
+
if (!symbol) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const type = checker.getDeclaredTypeOfSymbol(symbol);
|
|
1434
|
+
|
|
1435
|
+
const scalarMetadata = detectScalarMetadata(type, checker);
|
|
1436
|
+
if (scalarMetadata.scalarName && !scalarMetadata.isPrimitive) {
|
|
1437
|
+
detectedScalarNames.add(scalarMetadata.scalarName);
|
|
1438
|
+
const tsdocInfo = extractTsDocInfo(node, checker);
|
|
1439
|
+
detectedScalars.push({
|
|
1440
|
+
scalarName: scalarMetadata.scalarName,
|
|
1441
|
+
typeName: name,
|
|
1442
|
+
only: scalarMetadata.only,
|
|
1443
|
+
sourceFile: filePath,
|
|
1444
|
+
line: typeSourceLocation.line,
|
|
1445
|
+
description: tsdocInfo.description ?? null,
|
|
1446
|
+
});
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
let typeDirectives: ReadonlyArray<DirectiveInfo> | null = null;
|
|
1451
|
+
let actualType = type;
|
|
1452
|
+
|
|
1453
|
+
if (hasDirectiveMetadata(type)) {
|
|
1454
|
+
const directiveResult = detectDirectiveMetadata(type, checker);
|
|
1455
|
+
if (directiveResult.directives.length > 0) {
|
|
1456
|
+
typeDirectives = directiveResult.directives;
|
|
1457
|
+
}
|
|
1458
|
+
if (directiveResult.errors.length > 0) {
|
|
1459
|
+
for (const error of directiveResult.errors) {
|
|
1460
|
+
diagnostics.push({
|
|
1461
|
+
code: error.code,
|
|
1462
|
+
message: `Type '${name}': ${error.message}`,
|
|
1463
|
+
severity: "error",
|
|
1464
|
+
location: typeSourceLocation,
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
actualType = type;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
const kind = determineTypeKind(node, actualType, checker);
|
|
1472
|
+
// Get typeNode for union member extraction (only for type aliases)
|
|
1473
|
+
const typeAliasTypeNode = ts.isTypeAliasDeclaration(node)
|
|
1474
|
+
? node.type
|
|
1475
|
+
: undefined;
|
|
1476
|
+
const unionMembers = extractUnionMembers(actualType, typeAliasTypeNode);
|
|
1477
|
+
const inlineObjectResult = extractInlineObjectMembers({
|
|
1478
|
+
type: actualType,
|
|
1479
|
+
checker,
|
|
1480
|
+
globalTypeMappings,
|
|
1481
|
+
knownTypeNames,
|
|
1482
|
+
typeNode: typeAliasTypeNode,
|
|
1483
|
+
});
|
|
1484
|
+
const tsdocInfo = extractTsDocInfo(node, checker);
|
|
1485
|
+
|
|
1486
|
+
let implementedInterfaces: ReadonlyArray<string> | null = null;
|
|
1487
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
1488
|
+
if (kind === "graphqlInterface") {
|
|
1489
|
+
const interfaces = extractImplementsFromDefineInterface(
|
|
1490
|
+
node,
|
|
1491
|
+
sourceFile,
|
|
1492
|
+
checker,
|
|
1493
|
+
);
|
|
1494
|
+
if (interfaces.length > 0) {
|
|
1495
|
+
implementedInterfaces = interfaces;
|
|
1496
|
+
}
|
|
1497
|
+
} else {
|
|
1498
|
+
const interfaces = extractImplementsFromGqlTypeDef(
|
|
1499
|
+
node,
|
|
1500
|
+
sourceFile,
|
|
1501
|
+
checker,
|
|
1502
|
+
);
|
|
1503
|
+
if (interfaces.length > 0) {
|
|
1504
|
+
implementedInterfaces = interfaces;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
const metadata: TypeMetadata = {
|
|
1510
|
+
name,
|
|
1511
|
+
kind,
|
|
1512
|
+
sourceFile: filePath,
|
|
1513
|
+
sourceLocation: typeSourceLocation,
|
|
1514
|
+
exportKind: hasDefaultExport ? "default" : "named",
|
|
1515
|
+
description: tsdocInfo.description,
|
|
1516
|
+
deprecated: tsdocInfo.deprecated,
|
|
1517
|
+
directives: typeDirectives,
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
if (kind === "enum") {
|
|
1521
|
+
const enumMembers = extractStringLiteralUnionMembers(
|
|
1522
|
+
actualType,
|
|
1523
|
+
checker,
|
|
1524
|
+
);
|
|
1525
|
+
types.push({
|
|
1526
|
+
metadata,
|
|
1527
|
+
fields: [],
|
|
1528
|
+
unionMembers: null,
|
|
1529
|
+
inlineObjectMembers: null,
|
|
1530
|
+
enumMembers,
|
|
1531
|
+
implementedInterfaces: null,
|
|
1532
|
+
});
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const ignoreFields = detectIgnoreFieldsMetadata({ type, checker });
|
|
1537
|
+
|
|
1538
|
+
if (ignoreFields !== null && kind !== "union") {
|
|
1539
|
+
const allFieldNames = collectAllFieldNames(type, checker);
|
|
1540
|
+
const validationDiagnostics = validateIgnoreFields({
|
|
1541
|
+
typeName: name,
|
|
1542
|
+
ignoreFields,
|
|
1543
|
+
allFieldNames,
|
|
1544
|
+
sourceLocation: typeSourceLocation,
|
|
1545
|
+
});
|
|
1546
|
+
diagnostics.push(...validationDiagnostics);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
const fieldResult =
|
|
1550
|
+
kind === "union"
|
|
1551
|
+
? { fields: [], diagnostics: [] }
|
|
1552
|
+
: extractFieldsFromType({
|
|
1553
|
+
type: actualType,
|
|
1554
|
+
checker,
|
|
1555
|
+
globalTypeMappings,
|
|
1556
|
+
knownTypeNames,
|
|
1557
|
+
knownTypeSymbols,
|
|
1558
|
+
underlyingSymbolToTypeName,
|
|
1559
|
+
sourceFiles: scannedSourceFilesSet,
|
|
1560
|
+
scalarMappingTable,
|
|
1561
|
+
scalarMappingContext: name.endsWith("Input")
|
|
1562
|
+
? "input"
|
|
1563
|
+
: "output",
|
|
1564
|
+
ignoreFields,
|
|
1565
|
+
});
|
|
1566
|
+
const fields = fieldResult.fields;
|
|
1567
|
+
diagnostics.push(...fieldResult.diagnostics);
|
|
1568
|
+
|
|
1569
|
+
if (name.endsWith("Input") && kind === "union") {
|
|
1570
|
+
if (
|
|
1571
|
+
inlineObjectResult?.hasInlineObjects &&
|
|
1572
|
+
inlineObjectResult.hasNamedTypes
|
|
1573
|
+
) {
|
|
1574
|
+
diagnostics.push({
|
|
1575
|
+
code: "ONEOF_MIXED_MEMBERS",
|
|
1576
|
+
message: `Input union type '${name}' mixes inline object literals with named type references. Use only inline object literals for oneOf input types.`,
|
|
1577
|
+
severity: "error",
|
|
1578
|
+
location: {
|
|
1579
|
+
...typeSourceLocation,
|
|
1580
|
+
column: 1,
|
|
1581
|
+
},
|
|
1582
|
+
});
|
|
1583
|
+
} else if (
|
|
1584
|
+
inlineObjectResult?.hasNamedTypes &&
|
|
1585
|
+
!inlineObjectResult.hasInlineObjects
|
|
1586
|
+
) {
|
|
1587
|
+
diagnostics.push({
|
|
1588
|
+
code: "ONEOF_NAMED_TYPE_UNION",
|
|
1589
|
+
message: `Input union type '${name}' uses named type references instead of inline object literals. Use inline object pattern: type ${name} = { field1: Type1 } | { field2: Type2 }`,
|
|
1590
|
+
severity: "error",
|
|
1591
|
+
location: {
|
|
1592
|
+
...typeSourceLocation,
|
|
1593
|
+
column: 1,
|
|
1594
|
+
},
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const inlineObjectMembers = inlineObjectResult?.hasInlineObjects
|
|
1600
|
+
? inlineObjectResult.members
|
|
1601
|
+
: null;
|
|
1602
|
+
|
|
1603
|
+
const typeInfo: ExtractedTypeInfo = {
|
|
1604
|
+
metadata,
|
|
1605
|
+
fields,
|
|
1606
|
+
unionMembers: unionMembers ?? null,
|
|
1607
|
+
inlineObjectMembers,
|
|
1608
|
+
enumMembers: null,
|
|
1609
|
+
implementedInterfaces,
|
|
1610
|
+
};
|
|
1611
|
+
|
|
1612
|
+
types.push(typeInfo);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
if (ts.isExportDeclaration(node)) {
|
|
1616
|
+
const result = processExportDeclaration(
|
|
1617
|
+
node,
|
|
1618
|
+
sourceFile,
|
|
1619
|
+
filePath,
|
|
1620
|
+
checker,
|
|
1621
|
+
globalTypeMappings,
|
|
1622
|
+
knownTypeNames,
|
|
1623
|
+
knownTypeSymbols,
|
|
1624
|
+
underlyingSymbolToTypeName,
|
|
1625
|
+
scannedSourceFilesSet,
|
|
1626
|
+
scalarMappingTable,
|
|
1627
|
+
);
|
|
1628
|
+
types.push(...result.types);
|
|
1629
|
+
diagnostics.push(...result.diagnostics);
|
|
1630
|
+
for (const scalarName of result.detectedScalarNames) {
|
|
1631
|
+
detectedScalarNames.add(scalarName);
|
|
1632
|
+
}
|
|
1633
|
+
detectedScalars.push(...result.detectedScalars);
|
|
1634
|
+
}
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
return {
|
|
1639
|
+
types,
|
|
1640
|
+
diagnostics,
|
|
1641
|
+
detectedScalarNames: [...detectedScalarNames],
|
|
1642
|
+
detectedScalars,
|
|
1643
|
+
};
|
|
1644
|
+
}
|