Tooling Preview - Graindoc
Providing a better documentation workflow for Grain modules.
Between the beginning of my career in software development and today, a lot of progress has been made on the tooling we use to write code every day, like language servers, code formatters, and documentation generators. When you compile an already-existing language to WebAssembly, you get to benefit from all the tooling that already exists for that language—a huge boon to productivity!
When I got involved with Grain, I didn’t quite understand how much work would be required to get the developer experience up-to-snuff. We already have the Grain Language Server for VSCode (with Vim support coming soon), but we need to build out a lot more of this tooling!
Graindoc
As a team, we’ve struggled with keeping our standard library documentation updated. For us, it is easier to write code comments in our monorepo than markdown documentation in our website repository. This led me on an adventure to build our Graindoc tool. Graindoc will look familiar to anyone that has written JavaDoc or JSDoc in the past, but with some niceties provided by the compiler.
Let’s take a look at the anatomy of a Graindoc comment:
The first thing we’ll notice is the /** */
comment block. This signals to the compiler to parse the comment as a docblock instead of a multi-line comment (/* */
). Additionally, the docblock is applied to an exported function—the Graindoc tool only generates documentation for exports of a module.
Breaking down the parts of the docblock itself.
- Any lines that don’t begin with a
@something
annotation are treated as a single description and grouped together with a newline separator. - The
@param
annotation indicates a parameter to the function. These should be specified in order of the function parameters and the name specified before the colon (:
) must match the parameter name exactly. The content after the colon is the description of the parameter. - The
@returns
annotation provides a description for the return value of the annotated function. - The
@example
annotation can be used to add single-line examples for the function. These are rendered in a markdown code block annotated as the “grain” language. Multiple examples can be specified with separate annotations. - The
@since
annotation specifies a Semantic Version for the version in which this function was added. - The
@history
annotation is similar to@since
, but, in addition to the version, provides a description of a change that was made. While only 1@since
annotation can be specified, multiple@history
annotations can be used.
If you are familiar with a tool like TSDoc, you might be wondering why we don’t specify the type for the @param
or @returns
annotations; this is because Graindoc can actually use Grain’s strong type system in order to figure out the specific types of these and inject them into the output! Your type signatures will always be up-to-date with Graindoc because compilers are much better at type systems than humans. 😉
You can see the types in the Parameters and Returns tables in the output:
Extra annotations
The primary vehicle for development of Graindoc has been the Grain standard library, so we needed to add some additional annotations to produce our expected output.
@module
The @module
annotation allows you to add header documentation to a module. A module docblock can also contain @example
, @since
, and @history
annotations. Our array
module is a good example of this:
@section
The @section
annotation is used as a grouping mechanism. Any docblocks between a section and the next are grouped under that header. In the standard library documentation, we use these to separate “Types” and “Values”:
@deprecated
From time to time, we’ll need to deprecate things in the standard library. In fact, we’ve already had deprecations in the v0.3.x releases that will be removed in v0.4. This has led us to add the @deprecated
annotation that will produce a warning with the deprecation message.
Without annotations
Even if you decide to not add these docblocks to your code, you can still run Graindoc against your code to output function names and type signatures. This is the bare-minimum we can generate without any annotations and might be improved as more development happens on the tool. Running Graindoc on our Hash library without any annotations outputs:
Preview mode
We plan to release this tool in the v0.4 release—but it is already available if you build the Grain compiler from source on the main
branch. It will remain in “preview” for a while because we still are finding bugs and improvements to be made as we document our own standard library. We encourage you to try it out on your Grain code and give us feedback to make it better!
We have some additional features that we are excited to add in the future, such as surfacing the comments on hover using the Grain Language Server, but this is our first step in having a great developer experience for all Grain users!