From 982e60c516d71d60eb5eddf08a675725dbe89cdc Mon Sep 17 00:00:00 2001 From: Patrick McLaughlin <88669932+bitgopatmcl@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:55:16 -0500 Subject: [PATCH] feat: add support for parsing template strings Adds support for parsing basic template strings in `packages/openapi-generator`. * **Add template string parsing function** - Add `parseTemplateLiteral` function to handle template strings in `packages/openapi-generator/src/codec.ts`. - Update `parsePlainInitializer` to call `parseTemplateLiteral` when encountering a template literal. * **Add tests for template string parsing** - Add test cases for basic and compound template literals in `packages/openapi-generator/test/codec.test.ts`. --- packages/openapi-generator/src/codec.ts | 66 +++++++++++++++++++ packages/openapi-generator/test/codec.test.ts | 39 +++++++++++ 2 files changed, 105 insertions(+) diff --git a/packages/openapi-generator/src/codec.ts b/packages/openapi-generator/src/codec.ts index 79e39dda..979a824f 100644 --- a/packages/openapi-generator/src/codec.ts +++ b/packages/openapi-generator/src/codec.ts @@ -327,6 +327,70 @@ function parseArrayExpression( return E.right({ type: 'tuple', schemas: result }); } +function parseTemplateLiteral( + project: Project, + source: SourceFile, + template: swc.TemplateLiteral, +): E.Either { + const expressions: E.Either[] = template.expressions.map((expr) => { + const schemaE = parsePlainInitializer(project, source, expr); + if (E.isLeft(schemaE)) { + return schemaE; + } + const schema = schemaE.right; + if ( + schema.type === 'string' && + schema.enum !== undefined && + schema.enum.length === 1 + ) { + return E.right(String(schema.enum[0])); + } else if (schema.type === 'ref') { + const realInitE = findSymbolInitializer(project, source, schema.name); + if (E.isLeft(realInitE)) { + return realInitE; + } + const schemaE = parsePlainInitializer( + project, + realInitE.right[0], + realInitE.right[1], + ); + if (E.isLeft(schemaE)) { + return schemaE; + } + if (schemaE.right.type !== 'string' || schemaE.right.enum === undefined) { + return errorLeft('Template element must be string literal'); + } + return E.right(String(schemaE.right.enum[0])); + } else { + return errorLeft('Template element must be string literal'); + } + }); + + return pipe( + expressions, + E.sequenceArray, + E.flatMap((literals) => { + const quasis = template.quasis.map((quasi) => quasi.cooked); + const result = quasis.reduce( + (acc, quasi, index) => { + if (index < literals.length) { + acc.push(quasi ?? '', literals[index]!); + } else { + acc.push(quasi ?? ''); + } + return acc; + }, + [] as (string | Schema)[], + ); + + return E.right({ + type: 'string', + enum: [result.join('')], + }); + }), + ); +} + export function parsePlainInitializer( project: Project, source: SourceFile, @@ -348,6 +412,8 @@ export function parsePlainInitializer( return E.right({ type: 'undefined' }); } else if (init.type === 'TsConstAssertion' || init.type === 'TsAsExpression') { return parsePlainInitializer(project, source, init.expression); + } else if (init.type === 'TemplateLiteral') { + return parseTemplateLiteral(project, source, init); } else if ( init.type === 'Identifier' || init.type === 'MemberExpression' || diff --git a/packages/openapi-generator/test/codec.test.ts b/packages/openapi-generator/test/codec.test.ts index 6955c7d3..afe505a0 100644 --- a/packages/openapi-generator/test/codec.test.ts +++ b/packages/openapi-generator/test/codec.test.ts @@ -870,3 +870,42 @@ testCase('computed property is parsed', COMPUTED_PROPERTY, { enum: ['foo'], } }); + +const TEMPLATE_LITERAL = ` +import * as t from 'io-ts'; +const test = 'foo'; +export const FOO = \`\${test}bar\`; +`; + +testCase('basic template literal is parsed', TEMPLATE_LITERAL, { + FOO: { + type: 'string', + enum: ['foobar'], + }, + test: { + type: 'string', + enum: ['foo'], + }, +}); + +const MULTI_TEMPLATE_LITERAL = ` +import * as t from 'io-ts'; +const test = 'foo'; +const test2 = 'baz'; +export const FOO = \`aaa\${test}bar\${test2}bat\`; +`; + +testCase('compound template literal is parsed', MULTI_TEMPLATE_LITERAL, { + FOO: { + type: 'string', + enum: ['aaafoobarbazbat'], + }, + test: { + type: 'string', + enum: ['foo'], + }, + test2: { + type: 'string', + enum: ['baz'], + }, +});