Skip to content

refactor(codegen): introduce Block builder to replace raw string chains#380

Merged
nao1215 merged 1 commit intomainfrom
refactor/issue-310-codegen-builder-abstraction
Apr 17, 2026
Merged

refactor(codegen): introduce Block builder to replace raw string chains#380
nao1215 merged 1 commit intomainfrom
refactor/issue-310-codegen-builder-abstraction

Conversation

@nao1215
Copy link
Copy Markdown
Owner

@nao1215 nao1215 commented Apr 17, 2026

Summary

Introduce a small line-oriented Block builder (src/sqlode/codegen/builder.gleam) and migrate the worst <> chains in common.gleam, models.gleam, and adapter.gleam off of embedded-\n / hand-rolled \" \" indentation. The goal is readability — the generated output is byte-for-byte identical, as verified by the existing substring assertions, shellspec CLI tests, and integration tests.

Changes

  • src/sqlode/codegen/builder.gleam (new): opaque Block wrapping List(String) with line, lines, empty, blank, concat, indent, and render. indent leaves blank lines empty so generated code never has trailing whitespace.
  • test/codegen_builder_test.gleam (new): unit tests for each primitive, pinning indent(blank) behaviour.
  • src/sqlode/codegen/common.gleam: gleam_type and gleam_fn compose through builder.concat instead of embedding \n in string literals.
  • src/sqlode/codegen/models.gleam: render_enum_type now uses builder.indent for constructor / case-branch indentation; render_table_type, render_row_type, render_row_type_or_alias, render_semantic_type_aliases, and render_strong_type_wrappers collapse their multi-line string.join bodies into builder.concat blocks.
  • src/sqlode/codegen/adapter.gleam: render_decoder, render_adapter_query_result, render_adapter_exec, render_adapter_exec_rows, and render_adapter_exec_last_id now compose via builder.concat. Extract query_fn_signature so the shared pub fn name(db: ...) -> Result(...) line lives in one place.

Design Decisions

  • Rejected a Wadler-style Doc with soft breaks — generated Gleam is layout-deterministic and has no reflow requirement. A flat List(String) model is what the codebase already uses (string.join(list.flatten([...]), \"\\n\")); Block just centralises it.
  • Left the top-level assembly patterns (conditional import blocks, string.join(list.flatten([...]), \"\\n\")) untouched. Block is modelled on that idiom, so rewriting it would add churn without improving readability.
  • Kept render_field_decode_line as a one-line <> chain — it produces a single string with no \n, so the builder would only add noise.
  • gleam_type and gleam_fn keep their existing String-returning signatures; they just render through a builder internally. Callers don't change.

Limitations

  • builder.gleam lives under src/sqlode/codegen/, which is effectively internal to the sqlode binary (no external imports). It is not added to doc/capabilities.md.
  • The Block API is minimal; richer helpers (e.g., gleam_fn_block, gleam_type_block) could be added later if follow-up refactors want a more declarative form.

Closes #310

Summary by CodeRabbit

  • Refactor

    • Refactored code generation modules to use a new builder utility for assembling Gleam source code, replacing manual string concatenation and improving code consistency and maintainability.
  • Tests

    • Added comprehensive test coverage for the code generation builder, including validation of block creation, concatenation, indentation, and rendering functionality.

Add a small line-oriented `Block` builder and migrate the worst
offenders in the codegen modules away from `<>` chains with embedded
`\n` and hand-rolled indentation.

- New `src/sqlode/codegen/builder.gleam` exposes an opaque `Block`
  wrapping `List(String)` with `line`, `lines`, `empty`, `blank`,
  `concat`, `indent`, and `render`. Blank lines never gain trailing
  whitespace from `indent`, keeping generated code lint-clean.
- `common.gleam::gleam_type` / `gleam_fn` emit their type/function
  skeletons through `builder.concat` instead of embedding `\n` in
  string literals.
- `models.gleam::render_enum_type` uses `builder.indent` for the
  constructor and case-branch indentation that used to be hand-rolled
  with `"  "` / `"    "` prefixes. Table/row type and alias renderers
  are also reduced to `builder.concat` blocks.
- `adapter.gleam::render_decoder`, `render_adapter_query_result`,
  `render_adapter_exec`, `render_adapter_exec_rows`, and
  `render_adapter_exec_last_id` now compose their output via
  `builder.concat`. Extract `query_fn_signature` so the shared
  `pub fn name(db: ...) -> Result(...)` line lives in one place.
- New `test/codegen_builder_test.gleam` covers empty / line / lines /
  blank / concat / indent / render and pins `indent(blank)` behaviour.

Clean list-of-sections assembly (conditional imports, top-level
`string.join(list.flatten([...]))`) is left as-is — that idiom is the
mental model Block is built on, and rewriting it adds churn without
readability gains.

Closes #310
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: daef0b87-ce41-4926-85e1-d92f02958e33

📥 Commits

Reviewing files that changed from the base of the PR and between dab0027 and c731a91.

📒 Files selected for processing (5)
  • src/sqlode/codegen/adapter.gleam
  • src/sqlode/codegen/builder.gleam
  • src/sqlode/codegen/common.gleam
  • src/sqlode/codegen/models.gleam
  • test/codegen_builder_test.gleam

📝 Walkthrough

Walkthrough

This change introduces a new code generation builder module (builder.gleam) that provides a structured, line-oriented API for assembling Gleam source code. Multiple existing code generation modules (adapter.gleam, common.gleam, models.gleam) are refactored to use this builder instead of manual string concatenation. A comprehensive test suite validates the builder's functionality.

Changes

Cohort / File(s) Summary
New Builder Module
src/sqlode/codegen/builder.gleam
Introduces opaque Block type and API for composable code generation: empty(), line(), lines(), blank(), concat(), indent(), and render() for structured assembly of Gleam source text.
Code Generation Refactoring
src/sqlode/codegen/adapter.gleam, src/sqlode/codegen/common.gleam, src/sqlode/codegen/models.gleam
Migrated from manual string concatenation to builder.concat() and builder.render() across function signature rendering, type declarations, and code assembly in decoder, query result, enum, type alias, and table/row type generation.
Builder Test Suite
test/codegen_builder_test.gleam
Added 10 GleeUnit tests covering block construction, concatenation, indentation, identity properties, and empty line handling for the new builder module.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A builder hops in, bringing order and grace,
No more tangled strings in this code-gen place!
Lines concat and render in structures so clean,
With indents and blocks, the finest we've seen.
Tests validate each hop—refactoring's delight! ✨


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

@nao1215 nao1215 merged commit 6c99a2b into main Apr 17, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: introduce a codegen builder abstraction to replace raw string concatenation

1 participant