Style Guide#
SELF has a house style. It predates the checks described on this page, and the
checks exist to keep it rather than to define it. The reference for the style is
the tree at commit f3e1e57c, the last commit before the first AI assisted
contribution to this repository.
Formatting is not covered here. fprettify owns indentation, alignment, keyword
case, and line breaking, and fprettify-lint enforces it against
fprettify.config. This page covers what fprettify cannot see: how comments
and documentation are written.
Two scripts implement the rules below. Each rule has an identifier, so a failure message points back to a section of this page.
python3 .github/scripts/style_check.py # src/, test/, examples/
python3 .github/scripts/docs_style_check.py # docs/ and the root pages
Both accept --files to check an explicit list and --stats to report the
measured metrics without failing. Thresholds live in .github/style-rules.json.
In continuous integration the style-check workflow runs them over the files a
pull request changes, scoring each changed file in full.
Fortran#
F001, the license banner#
Every source file opens with the 25 line BSD-3 banner, ruled above and below by a
line of forward slashes. The canonical text is .github/license-header.txt.
Copy it from there rather than from a neighbouring source file, because eighteen
files in the tree carry a banner that has been truncated or corrupted and
copying one of those reproduces the damage.
The banner is compared against that file rather than probed for a marker string, because a substring test does not notice a banner that has been truncated or corrupted. Ten files in the tree are missing the warranty disclaimer and one has a mangled word in it, and none of that was visible before. The copyright year and the style of the quotation marks around "AS IS" vary harmlessly across the tree and are the only two substitutions applied before the comparison, and the year substitution replaces the four digits alone rather than the copyright marker with them. Line structure and indentation are preserved, so a banner whose text is intact but whose lines have been rejoined or reindented is still rejected.
F002, the FORD post mark#
API documentation is generated by FORD from comments marked with two exclamation
points, placed on the line after the object being documented. The pre mark !>
is not used in SELF. src/SELF_SupportRoutines.f90 still carries doxygen markup
from an earlier convention; it is a holdover and is not a model to follow.
subroutine Init_Lagrange_t(this,N,controlNodeType,M,targetNodeType)
!! Initialize an instance of the Lagrange_t class
!! On output, all of the attributes for the Lagrange_t class are allocated
!! and values are initialized according to the number of control points,
!! number of target points, and the types for the control and target nodes.
implicit none
class(Lagrange_t),intent(out) :: this
!! Lagrange_t class instance
integer,intent(in) :: N
!! The number of control points for interpolant
F003, plain punctuation#
Below the license banner, source uses ASCII punctuation. Em dashes, en dashes, typographic quotes, the ellipsis character, arrows, and pictographs are rejected. Where a sentence wants an em dash, use a comma, a colon, or two sentences.
Scientific notation is not affected. The superscripts and the dot operator in
unit expressions such as m s⁻¹ appear throughout the original source and are
permitted.
F004, comments are prose#
A comment is a sentence, not a document. Markdown headings, bold spans, and fenced code blocks inside a comment are rejected. Explain what the routine computes and why the approach was chosen; the surrounding code already shows what each statement does.
The house pattern for annotating an expression is a trailing comment naming the quantity:
flux(1,1) = this%rho0*s(2) ! density, x flux ; rho0*u
flux(2,1) = s(4)/this%rho0 ! x-velocity, x flux; p/rho0
Reasoning that does not fit on one line goes in a plain comment block immediately above the code it explains.
F005, no work in progress markers#
TODO, FIXME, XXX, and HACK do not belong in source, and neither does the
name of a tool that helped write it. Deferred work is recorded in an issue and
in the "Out of scope" section of the pull request that deferred it, where it can
be found again.
F006, fused end keywords#
Closing keywords are written without a space: endmodule, endsubroutine Foo,
endfunction Foo, endtype Foo, enddo, endif, endinterface.
F007, implicit none#
implicit none appears in every program unit: modules, main programs, and every
procedure. In a procedure it is placed after the docstring and before the dummy
argument declarations. This is a hard requirement of CLAUDE.md.
The declaration must belong to the scope that needs it. A scope is scanned only
as far as its own contains statement, so an implicit none inside an internal
procedure does not satisfy the program or procedure hosting it.
Parts of the source predate the requirement, so this rule reports genuine pre-existing gaps as well as new ones. Adding the missing declarations is tracked separately; do not silence the rule.
F101, comment density#
Files under src/, excluding the cpu, gpu, and apu backend directories,
carry at least one comment line for every twenty lines of code. The floor is set
from the least commented hand written file in the reference tree. The backend
directories are excluded because they are thin dispatch wrappers over the
portable implementation.
This rule guards against undocumented code entering the core. It is not a target to aim at; the core physics modules run three times the floor.
F102, comment line length#
A whole line comment is at most 132 characters, matching the limit in
fprettify.config. Trailing comments that share a line with code are not
measured, because the length of those lines is already governed by fprettify.
What is deliberately not checked#
Docstring coverage is a review expectation, not a mechanical gate. Measured across the reference tree, a quarter of the core modules document none of their procedures and the median module documents fewer than one in ten, so no threshold on coverage would separate the original style from anything else. New code is still expected to document what it computes; a reviewer asks for that, not a script.
Bullet lists in comments are likewise not rejected. The original source uses
them, for instance in the CGNS convention blocks of src/SELF_Mesh_2D_t.f90, so
there is no defensible line to draw.
Documentation#
Documentation is prose. It explains the mathematics in paragraphs, gives the equations in LaTeX, and shows the calling sequence in a Fortran block.
D001, no pictographs#
Emoji and dingbats do not appear in the documentation. Say in words what the symbol would have stood for.
D002, plain punctuation#
As in source, documentation uses ASCII punctuation. Em dashes, en dashes, typographic quotes, and the ellipsis character are rejected outside fenced code blocks.
D003, one title#
A page opens with exactly one level one heading, and it is the first content on the page. Leading blank lines are allowed; text before the title is not.
D004, heading depth#
Headings go no deeper than level four. A page that needs more depth is really two pages.
D005, no checklists#
Task list checkboxes do not belong in the handbook. Work is tracked in issues.
D006, reachable pages#
Every page under docs/ appears in the nav: block of mkdocs.yml. A page
absent from the navigation is published but unreachable, so a missing entry is
reported as a defect rather than left to be noticed later.
D100, emphasis density#
A page carries no more than fourteen bold spans per hundred lines of prose. The limit is the density of the most heavily emphasised hand written page in the reference tree, so a page that trips it is emphasising more than any page the original authors wrote.
Inline code is neutralized before the count, so a literal such as __shared__
or **kwargs is read as an identifier rather than as emphasis.
Emphasis is rare in SELF documentation because the sentence carries it. A paragraph of bold labels followed by fragments reads as a slide, not as an explanation.
What is deliberately not checked#
Two metrics that seem obvious were measured and discarded. The share of prose
lines that are list items, and the mean sentence length, both overlap completely
between the reference tree and the current documentation. Neither separates hand
written prose from generated prose, so enforcing a threshold on either would
produce failures that carry no information. They are still reported under
--stats for anyone investigating a page.
Voice#
Documentation speaks in the first person plural for what SELF and its authors do, and in the second person to the reader. Sentences are written one per line without hard wrapping. Lists are reserved for genuine enumerations such as a parameter list with units.
Mathematics uses MathJax. The established notation is \vec{s} for the solution
vector, \overleftrightarrow{f} for the conservative flux, and \vec{q} for the
source terms, related by the generic conservation law
which every model page restates and then specializes.
Read docs/Models/linear-shallow-water-model.md for the structure of a model
page and docs/Tutorials/LinearShallowWater/KelvinWaves.md for a tutorial before
writing a new one.
Testing the checks#
The checkers gate every pull request, so they carry their own tests:
The tests pin each rule against a small fixture rather than against the
repository, so they keep working as the tree changes. style-check runs them on
every invocation, which is what stops a change confined to .github/scripts/
from merging green without either checker having scored anything.
Running the checks locally#
The pre-commit hook runs both scripts alongside fprettify on the files a
commit touches.
To check the whole tree, or a single file:
python3 .github/scripts/style_check.py
python3 .github/scripts/style_check.py --files src/SELF_Lagrange_t.f90
python3 .github/scripts/docs_style_check.py --stats docs
A threshold in .github/style-rules.json is calibrated against the reference
tree. Do not raise one to make a contribution pass; change the contribution.