feat: field docs via a moduledoc marker (#41) - #68
Merged
Merged
Conversation
bamorim
force-pushed
the
feat-41-schema-docs
branch
from
August 25, 2026 20:59
df4f254 to
d6654d5
Compare
bamorim
force-pushed
the
feat-41-schema-docs
branch
5 times, most recently
from
August 25, 2026 23:10
a0e541d to
29d0d4d
Compare
Fields accept a :doc option (stripped before the Ecto macro runs). When the module's @moduledoc contains the <!-- typed_ecto_schema: fields --> marker, it is replaced at compile time with a markdown list describing every field: name, rendered typespec and the :doc text when present. The marker is specific enough to be the opt-in itself: no schema option, no application config, and the moduledoc is never touched without it. Being an HTML comment, it stays invisible in rendered docs even when left unreplaced. Independently of the marker, the generated t/0 gets a @TypeDoc with a Fields heading and the same list, but only when the module defines no @TypeDoc of its own: an open @TypeDoc is kept (with the marker interpolated in it like in the moduledoc) and @TypeDoc false is respected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bamorim
force-pushed
the
feat-41-schema-docs
branch
from
August 25, 2026 23:12
29d0d4d to
172f39e
Compare
bamorim
added a commit
that referenced
this pull request
Aug 25, 2026
bamorim
added a commit
that referenced
this pull request
Aug 25, 2026
* release: prepare 0.5.0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: note additional_types + polymorphic_embed interaction in changelog Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: warn that list fields' named types represent a single element A plural field name like :roles or :channels gets a named type that is the element union, not the list — call that out explicitly since the pluralized name reads as if it were the list type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: add #67 and #68 to the 0.5.0 changelog Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements field docs with a marker-replacement design: fields accept a
doc:option, and the module's@moduledocis scanned for a specific marker that gets replaced at compile time with a markdown list of the fields. The marker is specific enough to be the opt-in itself — no schema option, no application config, no experimental flag, and no fallback behavior of any kind.Usage
generates documentation equivalent to:
How it works
:docoption is stripped inSyntaxSugarbefore the Ecto macro runs (same mechanism as:null/:enforce), and accepted everywhere those are:field/3, associations, embeds, polymorphic embeds and@primary_key. Docs accumulate in a new@__typed_ecto_schema_docs__attribute.TypeBuilder.__set_moduledoc__/1reads@moduledocviaModule.get_attribute; if it is a string containing<!-- typed_ecto_schema: fields -->, the marker is replaced (String.replace/3) and the attribute put back. Anything else (no moduledoc,@moduledoc false, no marker) is left completely untouched.Macro.to_string/1, so::overrides,null: false, enum unions etc. all show up as they appear int/0.belongs_toforeign keys, timestamps included;__meta__skipped), with the:doctext appended where present. Abelongs_to:docattaches to the association entry only, not the generated foreign key.@moduledocmust appear beforetyped_schema(its conventional position). Documented.Generated
@typedocIndependently of the moduledoc marker, the generated
t/0gets a@typedocwith a## Fieldsheading and the same list, so field docs also show up int Person.t()in IEx and on the type in hexdocs — meaningdoc:text is never silently lost even without the marker. This only happens when the module defines no@typedocof its own:@typedoc(defined before the schema block) is kept untouched, with the marker interpolated in it the same way as in the@moduledoc;@typedoc falseis respected;@type t()is defined, so it attaches tot/0and never to anadditional_typesnamed type (covered by a test).Known gaps (pre-existing, left out of scope)
embeds_one/manywith an inlinedoblock already rejects:enforce/:nulltoday (the opts aren't stripped on that code path), and:docbehaves the same there.timestamps(doc: ...)is not supported (Ecto silently accepts it; we ignore it) — a single doc for both generated fields seemed of little value.No CHANGELOG entry, per the open release PR.
Closes #41
🤖 Generated with Claude Code