// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln import { TypeAssertion, PrimitiveTypeAssertion, PrimitiveValueTypeAssertion, RepeatedAssertion, SpreadAssertion, SequenceAssertion, OneOfAssertion, EnumAssertion, ObjectAssertion, TypeAssertionMap, CodegenContext } from '../../types'; function formatTypeName(ty: TypeAssertion, ctx: CodegenContext, typeName: string) { if (typeName.includes('.')) { return generateGraphQlCodeInner(ty, false, ctx, false); } return typeName; } function formatGraphQlCodeDocComment(ty: TypeAssertion | string, nestLevel: number) { let code = ''; const indent = ' '.repeat(nestLevel); const docComment = typeof ty === 'string' ? ty : ty.docComment; if (docComment) { if (0 <= docComment.indexOf('\n')) { code += `${indent}/**\n${indent} ${ docComment .split('\n') .map(x => x.trimLeft()) .join(`\n${indent} `)}\n${indent} */\n`; } else { code += `${indent}/** ${docComment} */\n`; } } return code; } function isNullableOneOf(ty: OneOfAssertion, ctx: CodegenContext) { const filtered = ty.oneOf.filter(x => !( x.kind === 'primitive' && (x.primitiveName === 'null' || x.primitiveName === 'undefined') || x.kind === 'primitive-value' && (x.value === null || x.value === void 0))); return (filtered.length === 1 && ty.oneOf.length !== 1 ? filtered[0] : null) ; } function generateGraphQlCodePrimitive(ty: PrimitiveTypeAssertion, ctx: CodegenContext) { switch (ty.primitiveName) { case 'number': return 'Float'; case 'integer': return 'Int'; case 'bigint': return 'BigInt'; case 'string': return 'String'; case 'boolean': return 'Boolean'; case 'undefined': case 'null': default: return 'Any'; // TODO: Any is invalid type. } // TODO: Function, DateStr, DateTimeStr } function generateGraphQlCodePrimitiveValue(ty: PrimitiveValueTypeAssertion, ctx: CodegenContext) { if (ty.value === null) { return 'Any'; // TODO: Any is invalid type. } if (ty.value === void 0) { return 'Any'; // TODO: Any is invalid type. } switch (typeof ty.value) { case 'number': return 'Float'; case 'bigint': return 'BigInt'; case 'string': return 'String'; case 'boolean': return 'Boolean'; default: return 'Any'; // TODO: Any is invalid type. } } function generateGraphQlCodeRepeated(ty: RepeatedAssertion, ctx: CodegenContext) { return (`[${ty.repeated.typeName ? formatTypeName(ty.repeated, ctx, ty.repeated.typeName) : generateGraphQlCodeInner(ty.repeated, false, ctx, false)}${ (ty.repeated.kind === 'optional' || ty.repeated.kind === 'one-of' && isNullableOneOf(ty.repeated, ctx)) ? '' : '!'}]` ); } function generateGraphQlCodeSpread(ty: SpreadAssertion, ctx: CodegenContext) { return ''; } function generateGraphQlCodeSequence(ty: SequenceAssertion, ctx: CodegenContext) { return '[Any]'; // TODO: Any is invalid type. } function generateGraphQlCodeOneOf(ty: OneOfAssertion, ctx: CodegenContext, isUnion: boolean) { const z = isNullableOneOf(ty, ctx); if (z) { return z.typeName ? z.typeName : generateGraphQlCodeInner(z, false, ctx, false); } else { if (isUnion) { return `${ty.oneOf .map(x => x.typeName ? x.typeName : generateGraphQlCodeInner(x, false, ctx, false)).join(' | ')}`; } else { return 'Any'; // TODO: Any is invalid type. } } } function generateGraphQlCodeEnum(ty: EnumAssertion, ctx: CodegenContext) { return (ty.typeName ? formatTypeName(ty, ctx, ty.typeName) : 'Any' ); } function generateGraphQlCodeObject(ty: ObjectAssertion, isInterface: boolean, ctx: CodegenContext) { if (ty.members.length === 0) { return '{}'; } const sep = '\n'; const memberLines = ty.members .map(x => `${formatGraphQlCodeDocComment(x[3] || '', ctx.nestLevel + 1)}${ ' '.repeat(ctx.nestLevel + 1)}${ x[0]}: ${ x[1].typeName ? formatTypeName(x[1], {...ctx, nestLevel: ctx.nestLevel + 1}, x[1].typeName) : generateGraphQlCodeInner(x[1], false, {...ctx, nestLevel: ctx.nestLevel + 1}, false)}${ (x[1].kind === 'optional' || x[1].kind === 'one-of' && isNullableOneOf(x[1], ctx)) ? '' : '!'}`); return ( `{\n${memberLines.join(sep)}${sep}${' '.repeat(ctx.nestLevel)}}` ); } function generateGraphQlCodeInner(ty: TypeAssertion, isInterface: boolean, ctx: CodegenContext, isUnion: boolean): string { let ret = ''; switch (ty.kind) { case 'optional': return generateGraphQlCodeInner(ty.optional, isInterface, ctx, false); case 'one-of': return generateGraphQlCodeOneOf(ty, ctx, isUnion); // TODO: inline union is invalid. case 'spread': return generateGraphQlCodeSpread(ty, ctx); case 'sequence': return generateGraphQlCodeSequence(ty, ctx); case 'never': ret = 'Any'; // TODO: Any is invalid type. break; case 'any': ret = 'Any'; // TODO: Any is invalid type. break; case 'unknown': ret = 'Any'; // TODO: Any is invalid type. break; case 'primitive': ret = generateGraphQlCodePrimitive(ty, ctx); break; case 'primitive-value': ret = generateGraphQlCodePrimitiveValue(ty, ctx); break; case 'repeated': ret = generateGraphQlCodeRepeated(ty, ctx); break; case 'enum': ret = generateGraphQlCodeEnum(ty, ctx); break; case 'object': ret = generateGraphQlCodeObject(ty, isInterface, ctx); break; case 'symlink': ret = ty.symlinkTargetName; break; case 'operator': throw new Error(`Unexpected type assertion: ${(ty as any).kind}`); default: throw new Error(`Unknown type assertion: ${(ty as any).kind}`); } return ret + ''; } export function generateGraphQlCode(types: TypeAssertionMap): string { let code = `\nscalar Any\nunion BigInt = String | Int\n\n`; const ctx = {nestLevel: 0}; for (const ty of types.entries()) { if (ty[1].ty.noOutput) { code += `scalar ${ty[0]}\n\n`; continue; } code += formatGraphQlCodeDocComment(ty[1].ty, ctx.nestLevel); if (ty[1].ty.kind === 'object') { code += `type ${ty[0]} ${ generateGraphQlCodeInner(ty[1].ty, true, ctx, false)}\n\n`; } else if (ty[1].ty.kind === 'enum') { const indent0 = ' '.repeat(ctx.nestLevel); const indent1 = ' '.repeat(ctx.nestLevel + 1); code += `enum ${ty[0]} {\n${ ty[1].ty.values .map(x => `${ formatGraphQlCodeDocComment(x[2] || '', ctx.nestLevel + 1)}${ indent1}${x[0]}\n`) .join('')}${indent0}}\n\n`; } else if (ty[1].ty.kind === 'never' && ty[1].ty.passThruCodeBlock) { // nothing to do } else { code += `union ${ty[0]} = ${generateGraphQlCodeInner(ty[1].ty, false, ctx, true)}\n\n`; } } return code; }