-
Notifications
You must be signed in to change notification settings - Fork 0
feat(typebox): add support for template literal types #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,12 +16,78 @@ export class TemplateLiteralTypeHandler extends BaseTypeHandler { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| return makeTypeCall('Any') | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For template literal types, we'll convert them to string patterns | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This is a simplified approach - TypeBox supports template literals with T.TemplateLiteral | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const templateText = typeNode.getText() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parts: ts.Expression[] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For simple cases like `Q${number}`, we can represent as a string pattern | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // In a more complete implementation, we might parse the template parts | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return makeTypeCall('TemplateLiteral', [ts.factory.createStringLiteral(templateText)]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add the head part (literal string before first substitution) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const head = typeNode.getHead() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headCompilerNode = head.compilerNode as ts.TemplateHead | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headText = headCompilerNode.text | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (headText) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(headText)])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Dropping empty head breaks TemplateLiteral shape; always include the head (even empty). TypeBox TemplateLiteral parts should start with a literal segment. Skipping an empty head can lead to arrays starting with a type, which can be rejected and alters semantics. Apply: - const headText = headCompilerNode.text
- if (headText) {
- parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(headText)]))
- }
+ const headText = headCompilerNode.text
+ parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(headText)]))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Process template spans (substitutions + following literal parts) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const templateSpans = typeNode.getTemplateSpans() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const span of templateSpans) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Access the compiler node to get type and literal | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const compilerNode = span.compilerNode as ts.TemplateLiteralTypeSpan | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add the type from the substitution | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (compilerNode.type) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle common type cases directly | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const typeKind = compilerNode.type.kind | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeKind === ts.SyntaxKind.StringKeyword) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push(makeTypeCall('String')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (typeKind === ts.SyntaxKind.NumberKeyword) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push(makeTypeCall('Number')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (typeKind === ts.SyntaxKind.LiteralType) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle literal types (e.g., 'A', 42, true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const literalType = compilerNode.type as ts.LiteralTypeNode | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (ts.isStringLiteral(literalType.literal)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| makeTypeCall('Literal', [ts.factory.createStringLiteral(literalType.literal.text)]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (ts.isNumericLiteral(literalType.literal)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| makeTypeCall('Literal', [ts.factory.createNumericLiteral(literalType.literal.text)]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push(makeTypeCall('String')) // fallback for other literals | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (typeKind === ts.SyntaxKind.UnionType) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For union types, we need to handle each type in the union | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const unionType = compilerNode.type as ts.UnionTypeNode | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const unionParts = unionType.types.map((t) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (t.kind === ts.SyntaxKind.LiteralType) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const literalType = t as ts.LiteralTypeNode | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (ts.isStringLiteral(literalType.literal)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return makeTypeCall('Literal', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ts.factory.createStringLiteral(literalType.literal.text), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return makeTypeCall('String') // fallback | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push(makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(unionParts)])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fallback for other types | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push(makeTypeCall('String')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add the literal part after the substitution | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const literalText = compilerNode.literal?.text | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (literalText) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(literalText)])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+78
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Don’t drop empty trailing literals; always append span literal text.
Apply: - const literalText = compilerNode.literal?.text
- if (literalText) {
- parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(literalText)]))
- }
+ const literalText = compilerNode.literal.text
+ parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(literalText)]))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If no parts were found, fallback to a simple string | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (parts.length === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return makeTypeCall('String') | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Return TemplateLiteral with array of parts | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return makeTypeCall('TemplateLiteral', [ts.factory.createArrayLiteralExpression(parts)]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+85
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty template should be
Apply: - // If no parts were found, fallback to a simple string
- if (parts.length === 0) {
- return makeTypeCall('String')
- }
+ // If no parts were found, return the exact empty string literal
+ if (parts.length === 0) {
+ return makeTypeCall('Literal', [ts.factory.createStringLiteral('')])
+ }
+
+ // Ensure first part is a Literal to satisfy TemplateLiteral contract
+ const first = parts[0]
+ const firstIsLiteral =
+ ts.isCallExpression(first) &&
+ ts.isPropertyAccessExpression(first.expression) &&
+ first.expression.expression.getText() === 'Type' &&
+ first.expression.name.getText() === 'Literal'
+ if (!firstIsLiteral) {
+ parts.unshift(makeTypeCall('Literal', [ts.factory.createStringLiteral('')]))
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.