tynder
Version:
TypeScript friendly Data validator for JavaScript.
247 lines (213 loc) • 8.05 kB
text/typescript
// 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;
}