From 589b9a0c2636d1523b7fe39479f17d697e87ae2b Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Feb 2024 15:45:13 -0800 Subject: [PATCH] Improve config validation --- src/gratsConfig.ts | 16 +++++++++ src/printSchema.ts | 17 ++++----- .../headerIsArrayWithNumber.invalid.ts | 6 ++++ ...eaderIsArrayWithNumber.invalid.ts.expected | 14 ++++++++ .../configOptions/invalidKey.invalid.ts | 6 ++++ .../invalidKey.invalid.ts.expected | 14 ++++++++ .../multipleInvalidKeys.invalid.ts | 6 ++++ .../multipleInvalidKeys.invalid.ts.expected | 14 ++++++++ .../nonNullableIsNull.invalid.ts | 6 ++++ .../nonNullableIsNull.invalid.ts.expected | 35 +++++++++++++++++++ src/tests/test.ts | 21 ++++++----- 11 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 src/tests/fixtures/configOptions/headerIsArrayWithNumber.invalid.ts create mode 100644 src/tests/fixtures/configOptions/headerIsArrayWithNumber.invalid.ts.expected create mode 100644 src/tests/fixtures/configOptions/invalidKey.invalid.ts create mode 100644 src/tests/fixtures/configOptions/invalidKey.invalid.ts.expected create mode 100644 src/tests/fixtures/configOptions/multipleInvalidKeys.invalid.ts create mode 100644 src/tests/fixtures/configOptions/multipleInvalidKeys.invalid.ts.expected create mode 100644 src/tests/fixtures/configOptions/nonNullableIsNull.invalid.ts create mode 100644 src/tests/fixtures/configOptions/nonNullableIsNull.invalid.ts.expected diff --git a/src/gratsConfig.ts b/src/gratsConfig.ts index f4e1dc11..595f7ed5 100644 --- a/src/gratsConfig.ts +++ b/src/gratsConfig.ts @@ -61,11 +61,27 @@ const DEFAULT_TYPESCRIPT_HEADER = `/** * Do not manually edit. Regenerate by running \`npx grats\`. */`; +const VALID_CONFIG_KEYS = new Set([ + "graphqlSchema", + "tsSchema", + "nullableByDefault", + "strictSemanticNullability", + "reportTypeScriptTypeErrors", + "schemaHeader", + "tsSchemaHeader", +]); + // TODO: Make this return diagnostics export function validateGratsOptions( options: ts.ParsedCommandLine, ): ParsedCommandLineGrats { const gratsOptions = { ...(options.raw?.grats ?? {}) }; + for (const key of Object.keys(gratsOptions)) { + if (!VALID_CONFIG_KEYS.has(key)) { + // TODO: Suggest similar? + throw new Error(`Grats: Unknown Grats config option \`${key}\``); + } + } if (gratsOptions.nullableByDefault === undefined) { gratsOptions.nullableByDefault = true; } else if (typeof gratsOptions.nullableByDefault !== "boolean") { diff --git a/src/printSchema.ts b/src/printSchema.ts index 46e58666..5562fe45 100644 --- a/src/printSchema.ts +++ b/src/printSchema.ts @@ -26,10 +26,7 @@ export function applyTypeScriptHeader( config: ConfigOptions, code: string, ): string { - if (config.tsSchemaHeader) { - return `${config.tsSchemaHeader}\n${code}`; - } - return code; + return formatHeader(config.tsSchemaHeader, code); } /** @@ -45,10 +42,7 @@ export function printGratsSDL( } export function applySDLHeader(config: ConfigOptions, sdl: string): string { - if (config.schemaHeader) { - return `${config.schemaHeader}\n${sdl}`; - } - return sdl; + return formatHeader(config.schemaHeader, sdl); } export function printSDLWithoutMetadata(doc: DocumentNode): string { @@ -67,3 +61,10 @@ export function printSDLWithoutMetadata(doc: DocumentNode): string { }); return print(trimmed); } + +function formatHeader(header: string | string[] | null, code: string): string { + if (header !== null) { + return `${Array.isArray(header) ? header.join("") : header}\n${code}`; + } + return code; +} diff --git a/src/tests/fixtures/configOptions/headerIsArrayWithNumber.invalid.ts b/src/tests/fixtures/configOptions/headerIsArrayWithNumber.invalid.ts new file mode 100644 index 00000000..29b5ccc1 --- /dev/null +++ b/src/tests/fixtures/configOptions/headerIsArrayWithNumber.invalid.ts @@ -0,0 +1,6 @@ +// {"schemaHeader": ["Hello", 1]} +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello: string; +} diff --git a/src/tests/fixtures/configOptions/headerIsArrayWithNumber.invalid.ts.expected b/src/tests/fixtures/configOptions/headerIsArrayWithNumber.invalid.ts.expected new file mode 100644 index 00000000..96344ff2 --- /dev/null +++ b/src/tests/fixtures/configOptions/headerIsArrayWithNumber.invalid.ts.expected @@ -0,0 +1,14 @@ +----------------- +INPUT +----------------- +// {"schemaHeader": ["Hello", 1]} +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello: string; +} + +----------------- +OUTPUT +----------------- +Grats: If the Grats config option `schemaHeader` is an array, it must be an array of strings. \ No newline at end of file diff --git a/src/tests/fixtures/configOptions/invalidKey.invalid.ts b/src/tests/fixtures/configOptions/invalidKey.invalid.ts new file mode 100644 index 00000000..9a71483e --- /dev/null +++ b/src/tests/fixtures/configOptions/invalidKey.invalid.ts @@ -0,0 +1,6 @@ +// {"invalidKey": "Oops"} +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello: string; +} diff --git a/src/tests/fixtures/configOptions/invalidKey.invalid.ts.expected b/src/tests/fixtures/configOptions/invalidKey.invalid.ts.expected new file mode 100644 index 00000000..4aabee9f --- /dev/null +++ b/src/tests/fixtures/configOptions/invalidKey.invalid.ts.expected @@ -0,0 +1,14 @@ +----------------- +INPUT +----------------- +// {"invalidKey": "Oops"} +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello: string; +} + +----------------- +OUTPUT +----------------- +Grats: Unknown Grats config option `invalidKey` \ No newline at end of file diff --git a/src/tests/fixtures/configOptions/multipleInvalidKeys.invalid.ts b/src/tests/fixtures/configOptions/multipleInvalidKeys.invalid.ts new file mode 100644 index 00000000..9673fbbf --- /dev/null +++ b/src/tests/fixtures/configOptions/multipleInvalidKeys.invalid.ts @@ -0,0 +1,6 @@ +// {"invalidKey": "Oops", "anotherInvalidKey": "Oops"} +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello: string; +} diff --git a/src/tests/fixtures/configOptions/multipleInvalidKeys.invalid.ts.expected b/src/tests/fixtures/configOptions/multipleInvalidKeys.invalid.ts.expected new file mode 100644 index 00000000..ca573386 --- /dev/null +++ b/src/tests/fixtures/configOptions/multipleInvalidKeys.invalid.ts.expected @@ -0,0 +1,14 @@ +----------------- +INPUT +----------------- +// {"invalidKey": "Oops", "anotherInvalidKey": "Oops"} +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello: string; +} + +----------------- +OUTPUT +----------------- +Grats: Unknown Grats config option `invalidKey` \ No newline at end of file diff --git a/src/tests/fixtures/configOptions/nonNullableIsNull.invalid.ts b/src/tests/fixtures/configOptions/nonNullableIsNull.invalid.ts new file mode 100644 index 00000000..64b0aa10 --- /dev/null +++ b/src/tests/fixtures/configOptions/nonNullableIsNull.invalid.ts @@ -0,0 +1,6 @@ +// {"tsSchema": null} +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello: string; +} diff --git a/src/tests/fixtures/configOptions/nonNullableIsNull.invalid.ts.expected b/src/tests/fixtures/configOptions/nonNullableIsNull.invalid.ts.expected new file mode 100644 index 00000000..fef421e5 --- /dev/null +++ b/src/tests/fixtures/configOptions/nonNullableIsNull.invalid.ts.expected @@ -0,0 +1,35 @@ +----------------- +INPUT +----------------- +// {"tsSchema": null} +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello: string; +} + +----------------- +OUTPUT +----------------- +-- SDL -- +type SomeType { + hello: String @metadata +} +-- TypeScript -- +import { GraphQLSchema, GraphQLObjectType, GraphQLString } from "graphql"; +export function getSchema(): GraphQLSchema { + const SomeTypeType: GraphQLObjectType = new GraphQLObjectType({ + name: "SomeType", + fields() { + return { + hello: { + name: "hello", + type: GraphQLString + } + }; + } + }); + return new GraphQLSchema({ + types: [SomeTypeType] + }); +} diff --git a/src/tests/test.ts b/src/tests/test.ts index d2c9518c..54d2fe1c 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -103,14 +103,19 @@ const testDirs = [ `${fixturesDir}/${fileName}`, path.join(__dirname, `../Types.ts`), ]; - const parsedOptions: ParsedCommandLineGrats = validateGratsOptions({ - options: {}, - raw: { - grats: options, - }, - errors: [], - fileNames: files, - }); + let parsedOptions: ParsedCommandLineGrats; + try { + parsedOptions = validateGratsOptions({ + options: {}, + raw: { + grats: options, + }, + errors: [], + fileNames: files, + }); + } catch (e) { + return e.message; + } // https://stackoverflow.com/a/66604532/1263117 const compilerHost = ts.createCompilerHost(