UNPKG

tynder

Version:

TypeScript friendly Data validator for JavaScript.

213 lines 8.83 kB
// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln import { escapeString } from '../../lib/escape'; import { SymbolPattern } from '../../lib/util'; function formatTypeName(typeName) { if (typeName.includes('.')) { const z = typeName.split('.'); let s = z[0]; for (let i = 1; i < z.length; i++) { s += `['${escapeString(z[i])}']`; } return `(${s})`; } return typeName; } function formatTypeScriptCodeDocComment(ty, nestLevel) { 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 generateTypeScriptCodePrimitive(ty, ctx) { // TODO: Function, DateStr, DateTimeStr switch (ty.primitiveName) { case 'integer': return 'number'; default: return ty.primitiveName; } } function generateTypeScriptCodePrimitiveValue(ty, ctx) { if (ty.value === null) { return 'null'; } if (ty.value === void 0) { return 'undefined'; } switch (typeof ty.value) { case 'string': return `'${escapeString(ty.value)}'`; case 'bigint': return `${ty.value.toString()}n`; default: return ty.value.toString(); } } function generateTypeScriptCodeRepeated(ty, ctx) { return (ty.repeated.kind === 'primitive' || ty.repeated.kind === 'never' || ty.repeated.kind === 'any' || ty.repeated.kind === 'unknown' || ty.repeated.kind === 'object' || ty.repeated.kind === 'symlink' || (ty.repeated.kind === 'one-of' && ty.repeated.typeName) ? `${ty.repeated.typeName ? formatTypeName(ty.repeated.typeName) : generateTypeScriptCodeInner(ty.repeated, false, ctx)}[]` : `Array<${ty.repeated.typeName ? formatTypeName(ty.repeated.typeName) : generateTypeScriptCodeInner(ty.repeated, false, ctx)}>`); } function generateTypeScriptCodeSpread(ty, ctx) { return ''; } function generateTypeScriptCodeSequence(ty, ctx) { if (0 < ty.sequence.filter(x => x.kind === 'spread' || x.kind === 'optional').length) { return 'any[]'; } return `[${ty.sequence .filter(x => x.kind !== 'spread' && x.kind !== 'optional') .map(x => x.typeName ? formatTypeName(x.typeName) : generateTypeScriptCodeInner(x, false, Object.assign(Object.assign({}, ctx), { nestLevel: ctx.nestLevel + 1 }))) .join(', ')}]`; } function generateTypeScriptCodeOneOf(ty, ctx) { return `(${ty.oneOf .map(x => x.typeName ? formatTypeName(x.typeName) : generateTypeScriptCodeInner(x, false, ctx)).join(' | ')})`; } function generateTypeScriptCodeOptional(ty, ctx) { return generateTypeScriptCodeInner(ty.optional, false, ctx); } function generateTypeScriptCodeEnum(ty, ctx) { return `(${ty.values.map(x => `${x[1]}`).join(' | ')})`; } function formatAdditionalPropsName(ak, i) { return (`[propName${i}: ${ak.map(x => typeof x === 'string' ? x : 'string').join(' | ')}]`); } function generateTypeScriptCodeObject(ty, isInterface, ctx) { var _a, _b; if (ty.members.filter(x => !(x[2])).length === 0 && ((_a = ty.additionalProps) === null || _a === void 0 ? void 0 : _a.filter(x => !(x[2])).length) === 0) { return '{}'; } // NOTE: Semicolon is always preferred. // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md const sep = isInterface ? ';\n' : ';\n'; const memberLines = ty.members.filter(x => !(x[2])) .map(x => `${formatTypeScriptCodeDocComment(x[3] || '', ctx.nestLevel + 1)}${' '.repeat(ctx.nestLevel + 1)}${SymbolPattern.test(x[0]) ? x[0] : `'${escapeString(x[0])}'`}${x[1].kind === 'optional' ? '?' : ''}: ${x[1].typeName ? formatTypeName(x[1].typeName) : generateTypeScriptCodeInner(x[1], false, Object.assign(Object.assign({}, ctx), { nestLevel: ctx.nestLevel + 1 }))}`); const additionalPropsLines = ((_b = ty.additionalProps) === null || _b === void 0 ? void 0 : _b.filter(x => !(x[2])).map((x, i) => `${formatTypeScriptCodeDocComment(x[3] || '', ctx.nestLevel + 1)}${' '.repeat(ctx.nestLevel + 1)}${formatAdditionalPropsName(x[0], i)}${x[1].kind === 'optional' ? '?' : ''}: ${x[1].typeName ? formatTypeName(x[1].typeName) : generateTypeScriptCodeInner(x[1], false, Object.assign(Object.assign({}, ctx), { nestLevel: ctx.nestLevel + 1 }))}`)) || []; const propsLines = memberLines.concat(additionalPropsLines); if (propsLines.length === 0) { return '{}'; } return (`{\n${propsLines.join(sep)}${sep}${' '.repeat(ctx.nestLevel)}}`); } function generateTypeScriptCodeInner(ty, isInterface, ctx) { switch (ty.kind) { case 'never': return 'never'; case 'any': return 'any'; case 'unknown': return 'unknown'; case 'primitive': return generateTypeScriptCodePrimitive(ty, ctx); case 'primitive-value': return generateTypeScriptCodePrimitiveValue(ty, ctx); case 'repeated': return generateTypeScriptCodeRepeated(ty, ctx); case 'spread': return generateTypeScriptCodeSpread(ty, ctx); case 'sequence': return generateTypeScriptCodeSequence(ty, ctx); case 'one-of': return generateTypeScriptCodeOneOf(ty, ctx); case 'optional': return generateTypeScriptCodeOptional(ty, ctx); case 'enum': return generateTypeScriptCodeEnum(ty, ctx); case 'object': return generateTypeScriptCodeObject(ty, isInterface, ctx); case 'symlink': return ty.symlinkTargetName; case 'operator': throw new Error(`Unexpected type assertion: ${ty.kind}`); default: throw new Error(`Unknown type assertion: ${ty.kind}`); } } export function generateTypeScriptCode(types) { let code = ''; const ctx = { nestLevel: 0 }; for (const ty of types.entries()) { if (ty[1].ty.noOutput) { continue; } code += formatTypeScriptCodeDocComment(ty[1].ty, ctx.nestLevel); if (ty[1].exported) { code += 'export '; } if (ty[1].isDeclare) { code += 'declare '; } if (ty[1].ty.kind === 'object') { code += `interface ${ty[0]}${ty[1].ty.baseTypes && ty[1].ty.baseTypes.length ? ` extends ${ty[1].ty.baseTypes .filter(x => x.typeName) .map(x => formatTypeName(x.typeName)) .join(', ')}` : ''} ${generateTypeScriptCodeInner(ty[1].ty, true, ctx)}\n\n`; } else if (ty[1].ty.kind === 'enum') { const indent0 = ' '.repeat(ctx.nestLevel); const indent1 = ' '.repeat(ctx.nestLevel + 1); let value = 0; code += `${ty[1].ty.isConst ? 'const ' : ''}enum ${ty[0]} {\n${ty[1].ty.values .map(x => `${formatTypeScriptCodeDocComment(x[2] || '', ctx.nestLevel + 1)}${indent1}${(() => { if (value !== null && x[1] === value) { value++; return `${x[0]}`; } else { if (typeof x[1] === 'number') { value = x[1] + 1; return `${x[0]} = ${x[1]}`; } else { return `${x[0]} = '${escapeString(x[1])}'`; } } })()},\n`) .join('')}${indent0}}\n\n`; } else if (ty[1].ty.kind === 'never' && ty[1].ty.passThruCodeBlock) { code += `${ty[1].ty.passThruCodeBlock}\n\n`; } else { code += `type ${ty[0]} = ${(ty[1].ty.originalTypeName ? formatTypeName(ty[1].ty.originalTypeName) : void 0) || generateTypeScriptCodeInner(ty[1].ty, false, ctx)};\n\n`; } } return code; } //# sourceMappingURL=typescript.js.map