Skip to content
This repository was archived by the owner on Apr 14, 2026. It is now read-only.

Commit 79c2020

Browse files
mromaszewiczclaude
andcommitted
refactor: replace runtime templates with annotated Go source files
Replace .tmpl template files for runtime code (types, params, helpers) with real Go source files in the runtime package. The runtime package is now the single source of truth -- compilable, testable, with full IDE support. When inlining (no external runtime package), the codegen emits ALL runtime code and runs a dead code elimination pass to strip unreachable declarations. This replaces the previous per-function tracking system (NeedParam, NeedHelper, NeedCustomType, etc.) with a simpler emit-everything-then-prune approach. Key changes: - Runtime source files in runtime/types/, runtime/params/, runtime/helpers/ with //oapi-runtime:function annotations - New runtime/embed.go exposes SourceFS for the codegen to read - New runtimeextract package: reads embedded .go files, strips package/imports, does qualifier substitution (types.Date -> Date) - New dce package: AST-based mark-and-sweep dead code elimination - Removed all runtime function tracking from CodegenContext - Removed template-based runtime code generation (generateHelper, generateParamFunctionsFromContext, etc.) - Simplified registry.go to only server/client/sender templates - Migrated tests from templates/test/ to runtime sub-packages - Moved structToFieldDict from style_simple to param helpers (fixes latent bug where 8 style templates referenced undeclared function) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a626867 commit 79c2020

90 files changed

Lines changed: 1172 additions & 3717 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

experimental/codegen/internal/clientgen_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,6 @@ func TestClientGenerator_FormEncoded(t *testing.T) {
136136
// Verify form-encoded body methods reference MarshalForm
137137
require.Contains(t, clientCode, "MarshalForm(body)")
138138

139-
// Verify we generate the form helper when needed
140-
ctx.NeedFormHelper(ops)
141-
formHelper, err := generateMarshalFormHelper()
142-
require.NoError(t, err, "Failed to generate form helper")
143-
require.NotEmpty(t, formHelper, "Form helper should be generated when form-encoded bodies exist")
144-
require.Contains(t, formHelper, "func MarshalForm(")
145-
require.Contains(t, formHelper, "func marshalFormImpl(")
146-
require.Contains(t, formHelper, "reflect.Value")
147-
148139
// Verify it generates WithFormdataBody method
149140
require.Contains(t, clientCode, "WithFormdataBody")
150141
}

experimental/codegen/internal/codegen.go

Lines changed: 28 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ package codegen
44
import (
55
"fmt"
66
"strings"
7-
"text/template"
87

98
"github.com/pb33f/libopenapi"
109
"github.com/pb33f/libopenapi/datamodel/high/base"
10+
"golang.org/x/tools/imports"
1111

12+
"github.com/oapi-codegen/oapi-codegen-exp/experimental/codegen/internal/dce"
13+
"github.com/oapi-codegen/oapi-codegen-exp/experimental/codegen/internal/runtimeextract"
1214
"github.com/oapi-codegen/oapi-codegen-exp/experimental/codegen/internal/templates"
15+
runtime "github.com/oapi-codegen/oapi-codegen-exp/experimental/codegen/internal/runtime"
1316
)
1417

1518
// Generate produces Go code from the parsed OpenAPI document.
@@ -150,8 +153,6 @@ func Generate(doc libopenapi.Document, specData []byte, cfg Configuration) (stri
150153
ctx.AddImportAlias(cfg.Generation.ModelsPackage.Path, cfg.Generation.ModelsPackage.Alias)
151154
}
152155

153-
// Register form helper if any operation has form-encoded bodies
154-
ctx.NeedFormHelper(ops)
155156
}
156157

157158
// Track whether shared error types have been generated to avoid duplication.
@@ -228,7 +229,6 @@ func Generate(doc libopenapi.Document, specData []byte, cfg Configuration) (stri
228229
ctx.AddImportAlias(cfg.Generation.ModelsPackage.Path, cfg.Generation.ModelsPackage.Alias)
229230
}
230231

231-
ctx.NeedFormHelper(webhookOps)
232232
}
233233
}
234234

@@ -258,7 +258,6 @@ func Generate(doc libopenapi.Document, specData []byte, cfg Configuration) (stri
258258
ctx.AddImportAlias(cfg.Generation.ModelsPackage.Path, cfg.Generation.ModelsPackage.Alias)
259259
}
260260

261-
ctx.NeedFormHelper(callbackOps)
262261
}
263262
}
264263

@@ -358,44 +357,23 @@ func Generate(doc libopenapi.Document, specData []byte, cfg Configuration) (stri
358357

359358
if cfg.Generation.RuntimePackage != nil {
360359
// Runtime package is configured — don't embed helpers, import them.
361-
// Add imports for whichever sub-packages are actually needed.
360+
// Always add all three sub-package imports; the Go compiler and goimports
361+
// will strip any that end up unused.
362362
ctx.AddImportAlias(cfg.Generation.RuntimePackage.TypesImport(), "oapiCodegenTypesPkg")
363-
if ctx.HasAnyParams() {
364-
ctx.AddImportAlias(cfg.Generation.RuntimePackage.ParamsImport(), "oapiCodegenParamsPkg")
365-
}
366-
if len(ctx.RequiredHelpers()) > 0 {
367-
ctx.AddImportAlias(cfg.Generation.RuntimePackage.HelpersImport(), "oapiCodegenHelpersPkg")
368-
}
363+
ctx.AddImportAlias(cfg.Generation.RuntimePackage.ParamsImport(), "oapiCodegenParamsPkg")
364+
ctx.AddImportAlias(cfg.Generation.RuntimePackage.HelpersImport(), "oapiCodegenHelpersPkg")
369365
} else {
370-
// Resolve param template dependencies first — this may register
371-
// additional custom types (e.g., Date) that need to be emitted.
372-
ctx.GetRequiredParamTemplates()
373-
374-
// Emit custom type templates (Date, Email, UUID, File, Nullable, etc.)
375-
for _, templateName := range ctx.RequiredCustomTypes() {
376-
typeCode := ctx.loadAndRegisterCustomType(templateName)
377-
if typeCode != "" {
378-
output.AddType(typeCode)
379-
}
380-
}
381-
382-
// Emit param functions (typesPrefix is empty when embedded — Date is in same package)
383-
paramFuncs, err := generateParamFunctionsFromContext(ctx, "")
366+
// Inline mode: emit all runtime code, DCE will remove unused declarations.
367+
runtimeCode, runtimeImports, err := runtimeextract.ExtractAllInline(runtime.SourceFS)
384368
if err != nil {
385-
return "", fmt.Errorf("generating param functions: %w", err)
386-
}
387-
if paramFuncs != "" {
388-
output.AddType(paramFuncs)
369+
return "", fmt.Errorf("extracting runtime code: %w", err)
389370
}
390-
391-
// Emit helper templates (e.g., marshal_form)
392-
for _, helperName := range ctx.RequiredHelpers() {
393-
helperCode, err := generateHelper(helperName, ctx)
394-
if err != nil {
395-
return "", fmt.Errorf("generating helper %s: %w", helperName, err)
396-
}
397-
if helperCode != "" {
398-
output.AddType(helperCode)
371+
output.AddType(runtimeCode)
372+
for _, imp := range runtimeImports {
373+
if imp.Alias != "" {
374+
ctx.AddImportAlias(imp.Path, imp.Alias)
375+
} else {
376+
ctx.AddImport(imp.Path)
399377
}
400378
}
401379
}
@@ -404,101 +382,26 @@ func Generate(doc libopenapi.Document, specData []byte, cfg Configuration) (stri
404382
// Transfer all imports from ctx to output
405383
output.AddImports(ctx.Imports())
406384

407-
return output.Format()
408-
}
409-
410-
// generateHelper generates a helper template by name and registers its imports on the context.
411-
func generateHelper(name string, ctx *CodegenContext) (string, error) {
412-
switch name {
413-
case "marshal_form":
414-
ctx.AddTemplateImports(templates.MarshalFormHelperTemplate.Imports)
415-
return generateMarshalFormHelper()
416-
case "json_merge":
417-
ctx.AddTemplateImports(templates.JSONMergeHelperTemplate.Imports)
418-
return generateJSONMergeHelper()
419-
default:
420-
return "", fmt.Errorf("unknown helper: %s", name)
421-
}
422-
}
423-
424-
// generateMarshalFormHelper generates the marshalForm helper function.
425-
func generateMarshalFormHelper() (string, error) {
426-
tmplInfo := templates.MarshalFormHelperTemplate
427-
content, err := templates.TemplateFS.ReadFile("files/" + tmplInfo.Template)
428-
if err != nil {
429-
return "", fmt.Errorf("reading form helper template: %w", err)
430-
}
431-
432-
tmpl, err := template.New(tmplInfo.Name).Parse(string(content))
433-
if err != nil {
434-
return "", fmt.Errorf("parsing form helper template: %w", err)
435-
}
436-
437-
var result strings.Builder
438-
if err := tmpl.Execute(&result, nil); err != nil {
439-
return "", fmt.Errorf("executing form helper template: %w", err)
440-
}
441-
442-
return result.String(), nil
443-
}
444-
445-
// generateJSONMergeHelper generates the JSONMerge helper function.
446-
func generateJSONMergeHelper() (string, error) {
447-
tmplInfo := templates.JSONMergeHelperTemplate
448-
content, err := templates.TemplateFS.ReadFile("files/" + tmplInfo.Template)
385+
formatted, err := output.Format()
449386
if err != nil {
450-
return "", fmt.Errorf("reading json merge helper template: %w", err)
387+
return "", err
451388
}
452389

453-
tmpl, err := template.New(tmplInfo.Name).Parse(string(content))
454-
if err != nil {
455-
return "", fmt.Errorf("parsing json merge helper template: %w", err)
456-
}
457-
458-
var result strings.Builder
459-
if err := tmpl.Execute(&result, nil); err != nil {
460-
return "", fmt.Errorf("executing json merge helper template: %w", err)
461-
}
462-
463-
return result.String(), nil
464-
}
465-
466-
// generateParamFunctionsFromContext generates the parameter styling/binding functions based on CodegenContext usage.
467-
// typesPrefix is prepended to Date/DateFormat references in param templates; empty when embedded.
468-
func generateParamFunctionsFromContext(ctx *CodegenContext, typesPrefix string) (string, error) {
469-
if !ctx.HasAnyParams() {
470-
return "", nil
471-
}
472-
473-
var result strings.Builder
474-
475-
requiredTemplates := ctx.GetRequiredParamTemplates()
476-
477-
for _, tmplInfo := range requiredTemplates {
478-
content, err := templates.TemplateFS.ReadFile("files/" + tmplInfo.Template)
390+
// Run DCE to remove unused runtime declarations (only relevant in inline mode).
391+
if cfg.Generation.RuntimePackage == nil {
392+
formatted, err = dce.EliminateDeadCode(formatted)
479393
if err != nil {
480-
return "", fmt.Errorf("reading param template %s: %w", tmplInfo.Template, err)
394+
return "", fmt.Errorf("dead code elimination: %w", err)
481395
}
482-
483-
tmpl, err := template.New(tmplInfo.Name).Funcs(template.FuncMap{
484-
"typesPrefix": func() string { return typesPrefix },
485-
}).Parse(string(content))
396+
// DCE re-prints via go/printer which changes formatting; normalize with goimports.
397+
reformatted, err := imports.Process("", []byte(formatted), nil)
486398
if err != nil {
487-
return "", fmt.Errorf("parsing param template %s: %w", tmplInfo.Template, err)
488-
}
489-
490-
if err := tmpl.Execute(&result, nil); err != nil {
491-
return "", fmt.Errorf("executing param template %s: %w", tmplInfo.Template, err)
399+
return "", fmt.Errorf("formatting after DCE: %w", err)
492400
}
493-
result.WriteString("\n")
494-
}
495-
496-
// Register param imports on the context
497-
for _, imp := range ctx.GetRequiredParamImports() {
498-
ctx.AddImportAlias(imp.Path, imp.Alias)
401+
formatted = string(reformatted)
499402
}
500403

501-
return result.String(), nil
404+
return formatted, nil
502405
}
503406

504407
// generateType generates Go code for a single schema descriptor.
@@ -1072,7 +975,6 @@ func generateUnionTypeCommon(gen *TypeGenerator, desc *SchemaDescriptor, isOneOf
1072975
if len(fixedFields) > 0 {
1073976
gen.AddImport("fmt")
1074977
}
1075-
gen.NeedHelper("json_merge")
1076978

1077979
code, err := GenerateUnionCode(cfg)
1078980
if err != nil {

0 commit comments

Comments
 (0)