Documentation and Editor Tooling
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-19. The tools on this page are not
part of the language: they are community and vendor tools that read Fortran source. They were installed inside a Linux container (aarch64) with
pip or apt, nothing on the host, and every output shown is real output. Versions: FORD 7.0.13, fortls 3.2.2, fprettify 0.3.7,
findent 4.3.5, Fortitude 0.9.2 (package fortitude-lint), fpm 0.13.0, CMake 3.31.6, Meson 1.7.0 (the current Meson release is 1.12), and
gfortran 16.2. Where tools are described as community tools, that is a statement about who maintains them, not about quality.
The documentation-commented heatflow sources used below were also built and run with fpm run under
-std=f2023 -Wall -Wextra -pedantic -fcheck=all; the output is identical to the undocumented project (steps=2000 residual= 1.38E-04,
max temperature= 0.139), because comments do not change the program. LFortran, Intel ifx and NAG were not used here, so nothing below
claims how they behave. The tools on this page work on source text and do not need any particular compiler, except that the
compile_commands.json section shows what each build system writes.
For the IDEs and editors themselves, see IDEs & Tools. The page follows the way a project grows. First the comments that become documentation (FORD), then the editor-side tools that understand the
project (the fortls language server), then the formatters and linters that keep the source consistent, then the build-system output that
tools use to learn compile flags (compile_commands.json), and finally the places where the community meets. Code layout and naming
conventions are in Best Practices and Style, builds in
Fortran Package Manager (fpm) and
Building with Make, CMake and Meson, compilers and warnings in
Compilers and Compiler Options, and test and debug tools in
Testing, Debugging and Profiling.
The standard has no documentation format
Fortran defines no documentation-comment syntax. A comment starts with ! in free form (S 6.3.2.3) and the processor ignores it, so every
documentation convention is a convention of a tool. Doxygen can read Fortran, but the tool built for modern Fortran is FORD
(FORtran Documenter), which understands modules, submodules, derived types, type-bound procedures, generic interfaces, use relationships and
call trees, none of which a generic comment extractor sees. FORD is a Python program, installed with pip install FORD; it needs the
Graphviz dot program for call graphs and the pcpp package for preprocessing (installed together with FORD).
FORD doc comments
FORD attaches a comment to the entity it sits next to. The comment markers are configurable; the defaults, read from the installed FORD 7.0.13
settings, are docmark = "!", predocmark = ">" and two alternative marks. In practice this gives two forms:
-
Before the entity: a comment whose second character is the predoc mark,
!>, directly above a module, type, procedure or variable. -
After the entity: a comment whose second character is the doc mark,
!!, on the lines following the declaration, or on the same line after a declaration (the!!form documents arguments and components inline).
The text is Markdown (Python-Markdown with FORD extensions). FORD recognises the admonition tags @note, @warning, @bug, @todo and @history, and inline TeX inside ... or \( ... \) is rendered with MathJax in the browser. Below is the heatflow kernels module
as it was documented for this page; the code is unchanged apart from the comments.
!> Stencil kernels shared by the solvers, the parallel variants and the C API.
!>
!> Both kernels update the interior points of a rectangular grid and report
!> the largest change as a residual:
!>
!> $$ u^{n+1}_{i,j} = u^n_{i,j} + \alpha\,\Delta t\,\nabla^2 u^n_{i,j} $$
!>
!> @note Boundary points are never touched; apply the boundary condition
!> with `grid_t%apply_boundary` before each sweep.
module heatflow_kernels
use heatflow_kinds, only: dp, ip
implicit none
private
public :: jacobi_sweep, ftcs_sweep
contains
!> One Jacobi relaxation sweep of the Laplace equation (interior points).
!>
!> @warning `unew` must have the same shape as `u`.
pure subroutine jacobi_sweep(u, unew, residual)
real(dp), intent(in) :: u(:, :) !! Current temperature field.
real(dp), intent(inout) :: unew(:, :) !! Updated field (interior points only).
real(dp), intent(out) :: residual !! Largest absolute change of any point.
integer :: i, j
residual = 0.0_dp
do j = 2, size(u, 2) - 1
do i = 2, size(u, 1) - 1
unew(i, j) = 0.25_dp*(u(i - 1, j) + u(i + 1, j) + u(i, j - 1) + u(i, j + 1))
residual = max(residual, abs(unew(i, j) - u(i, j)))
end do
end do
end subroutine jacobi_sweep
!> One explicit FTCS step of \(u_t = \alpha (u_{xx} + u_{yy})\) (interior points).
!>
!> The scheme is stable only for \( \alpha\,\Delta t\,(1/\Delta x^2 + 1/\Delta y^2) \le 1/2 \).
pure subroutine ftcs_sweep(u, unew, alpha, dt, dx, dy, residual)
real(dp), intent(in) :: u(:, :) !! Current temperature field.
real(dp), intent(inout) :: unew(:, :) !! Updated field (interior points only).
real(dp), intent(in) :: alpha !! Thermal diffusivity.
real(dp), intent(in) :: dt !! Time step.
real(dp), intent(in) :: dx, dy !! Grid spacings.
real(dp), intent(out) :: residual !! Largest absolute change of any point.
integer :: i, j
residual = 0.0_dp
do j = 2, size(u, 2) - 1
do i = 2, size(u, 1) - 1
unew(i, j) = u(i, j) + alpha*dt*( &
(u(i - 1, j) - 2.0_dp*u(i, j) + u(i + 1, j))/dx**2 + &
(u(i, j - 1) - 2.0_dp*u(i, j) + u(i, j + 1))/dy**2)
residual = max(residual, abs(unew(i, j) - u(i, j)))
end do
end do
end subroutine ftcs_sweep
end module heatflow_kernels
The kinds module and the grid type were documented in the same way (!> above the module, !> above type :: grid_t, and a trailing !!
on each component and kind parameter):
!> Kind parameters shared by every heatflow module.
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64
implicit none
private
public :: sp, dp, ip, lp
integer, parameter :: sp = real32 !! Single precision real kind.
integer, parameter :: dp = real64 !! Double precision real kind used for temperatures.
integer, parameter :: ip = int32 !! Default integer kind for grid sizes.
integer, parameter :: lp = int64 !! Integer kind for step counters.
end module heatflow_kinds
A few rules that are easy to get wrong:
-
The doc block must be adjacent: a blank line between
!>lines and the entity detaches it. A blank!>line inside the block is fine, it is a paragraph break. -
For arguments,
!!on the same line is the compact form;!>above a declaration also works but is longer. Both forms attach to the variable, and the documentation page lists them in the Arguments table with type, intent, optional and attributes. -
Only entities FORD is told to display are documented. The
displaysetting defaults topublicandprotected, soprivatehelpers do not appear unlessdisplayincludesprivate. This is whyheatflow_kernelsusesprivateby default with an explicitpubliclist: the same declarations that make the module API small also make the generated documentation small. -
Doc comments are comments to the compiler. They never cause a compile error.
The project file
FORD reads its settings from a project file. Two layouts are supported.
A Markdown file. The settings are YAML-like front matter between --- lines and the body becomes the front page of the site. This is the
traditional layout (ford heatflow.md):
---
project: heatflow
summary: 2D heat diffusion on a rectangular plate
author: Heatflow authors
src_dir: ./src
./app
output_dir: ./doc
page_dir: ./pages
display: public
protected
source: false
graph: true
search: true
preprocess: false
---
Heatflow solves the 2D heat equation with Jacobi and FTCS solvers.
@note This text is the project landing page and is written in Markdown.
A TOML table in fpm.toml. The options can also live in the [extra.ford] table of the fpm manifest, so a fpm project keeps one
file. FORD reads it when it is given the manifest (ford fpm.toml), and prints Reading Ford options from /work/fordproj/fpm.toml. The
manifest of the documented heatflow copy contains:
[extra.ford]
project = "heatflow"
summary = "2D heat diffusion on a rectangular plate"
src_dir = ["src", "app"]
output_dir = "doc-toml"
display = ["public", "protected"]
graph = true
source = false
fpm itself ignores [extra.<name>] tables, so adding the table does not change the build. The same names are available as command-line options
for the common ones (ford --help lists -d/--src_dir, -p/--page_dir, -o/--output_dir, -s/--css, -r/--revision, --exclude,
--exclude_dir, -e/--extensions, -m/--macro, -I/--include, -w/--warn, -g/--graph, --no-search, -q/--quiet, --debug,
--config, -L/--external_links, --externalize); options given on the command line override the file.
The settings that matter most (defaults taken from the installed FORD 7.0.13 ProjectSettings):
| Setting | Default | Meaning |
|---|---|---|
|
|
directories scanned for source files |
|
none; |
files and directories to skip |
|
|
extensions treated as free-form source |
|
|
extensions treated as fixed form |
|
none |
additional file types and their comment character, for example shell scripts |
|
|
which access levels are documented ( |
|
|
generate call, uses and inheritance graphs (needs Graphviz) |
|
|
include the source code in the site ( |
|
|
build a client-side search index |
|
|
run the C-style preprocessor ( |
|
none |
preprocessor macros, for example |
|
|
the doc-comment markers (so |
|
none |
a directory of Markdown pages published beside the API reference |
|
|
ordering of lists ( |
|
|
print a warning for each undocumented entity (same as |
|
|
write |
|
|
number of processes used while parsing |
|
empty |
metadata shown in the site header and footer |
|
very large |
limits on graph size for big projects |
|
none |
extra MathJax configuration |
Running FORD
python3 -m venv .venv && .venv/bin/pip install FORD==7.0.13 fortls==3.2.2 fprettify
export PATH="$PWD/.venv/bin:$PATH"
ford heatflow.md
The real output (progress bars condensed to the last line of each stage):
Parsing files ━━━━━━━ 100% 12/12 0:00:00 0:00:… src/heatflow_kinds.f90
Correlating information from different parts of your project...
...done in 0.001s
Processing comments ━━━━━━━━━━━━━━━━━━━━━━━━ 100% 211/211 0:00:00 0:00:00 lp
Processing pages ━━━━━━━━━━━━━━━━ 100% 1/1 0:00:00 0:00:00 pages/index.md
Creating HTML documentation... done in 0.000s
Generating graphs ━━━━━━━━━━━━━━━━ 100% 39/39 0:00:00 0:00:00 write_stream
Creating search index ━━━━━━━━━━━━━ 100% 41/41 0:00:00 0:00:00 page/index.html
Writing files ━━━━━━━━━━━━━ 100% 48/48 0:00:00 0:00:00 doc/search.html
Browse the generated documentation: file:///work/fordproj/doc/index.html
The output directory is a static site that can be published as is (GitHub Pages, any web server). It contains one directory per kind of entity:
module, proc, program, type, interface, namelist, blockdata, sourcefile, page, lists (index pages), and search.html with its
index, plus modules.json for cross-project links. For heatflow, doc/module contains one page per heatflow_* module, doc/proc contains
jacobi_sweep.html, ftcs_sweep.html, make_grid.html and the other procedures, and doc/type the derived types. The Arguments table of
jacobi_sweep is generated from the declarations and the !! comments:
Arguments
Type Intent Name Description
real(kind=dp) in u(:,:) Current temperature field.
real(kind=dp) inout unew(:,:) Updated field (interior points only).
real(kind=dp) out residual Largest absolute change of any point.
(The columns are the text of the HTML table with the markup removed.) On the module page, the @note becomes a "Note" box and
the TeX is typeset in the browser.
Graphs
With graph: true (or -g), FORD runs Graphviz and embeds SVG graphs: for each module, a Uses graph and a Used by graph; for procedures
a Calls and Called by graph; for types, inheritance and containment; and for the whole project a module-dependency and a call graph.
For heatflow_kernels the generated graphs say that it uses heatflow_kinds, which uses iso_fortran_env, and that it is used by
heatflow_capi, heatflow_ftcs and heatflow_jacobi. This is the dependency picture that
Modules and Submodules describes in prose, drawn automatically, which is a quick
way to find a circular-looking design before the compiler complains. Without Graphviz installed, the graphs cannot be generated; install it
(apt install graphviz, or the equivalent for your system) or set graph: false.
Undocumented entities
ford -w (or warn: true) prints one warning per undocumented entity. On the partly documented heatflow copy it starts like this:
Warning: Undocumented boundprocedure 'unknown' in file 'heatflow_grid.f90'
Warning: Undocumented variable 'nx' in file 'heatflow_grid.f90'
Warning: Undocumented variable 'ny' in file 'heatflow_grid.f90'
Warning: Undocumented variable 'lx' in file 'heatflow_grid.f90'
Warning: Undocumented proc 'unknown' in file 'heatflow_grid.f90'
Warning: Undocumented program 'unknown' in file 'main.f90'
Warning: Undocumented variable 'i' in file 'heatflow_kernels.f90'
Warning: Undocumented module 'unknown' in file 'heatflow_boundary.f90'
Two things stand out. The entity name is often printed as unknown for units, so the file name is the useful part. And local variables
such as the loop indices i and j are reported too, so the list is a to-do list and not a target of zero; use it to find undocumented
public entities, which display limits the site to anyway. Note that a missing warning does not mean the text is good:
FORD only checks that a comment exists.
Pages and cross-links
page_dir points at a directory of Markdown files with front matter (title: and optionally ordered_subpage:), which are published
beside the API reference. Use pages for a user guide, an installation page and a theory page; use doc comments for the API. A comment can link to
another entity with -style double brackets, which FORD resolves against the project (and against projects imported with
external/-L, using the modules.json that externalize writes).
Practical notes
-
Run FORD in continuous integration and fail on errors, not on
-wwarnings, then publish thedocdirectory with the same pipeline as the site. -
The documentation lives with the code: Fortran files are the single source, and FORD reads the same
srcdirectory asfpm. -
Because the text is Markdown, put code samples in fenced blocks and keep lines readable in the source, where the comments are read most of the time.
-
The generated pages show the declarations as the compiler sees them; a
privatecomponent with a public type is hidden unlessdisplayincludesprivate.
The fortls language server
fortls is a language server for Fortran: a program that speaks the Language Server Protocol (LSP) over standard input and output, so any
editor with an LSP client can offer code intelligence for Fortran without a plug-in per feature. It is a Python package (pip install fortls),
maintained by the Fortran community. Probed with a real initialize request over stdio (a small Python script that frames the JSON-RPC
message), fortls 3.2.2 advertises these capabilities:
completionProvider
definitionProvider
documentSymbolProvider
hoverProvider
implementationProvider
referencesProvider
renameProvider
textDocumentSync
workspaceSymbolProvider
fortls also answers textDocument/signatureHelp and publishes diagnostics, as the debug commands below show; the code-action support
is marked experimental in its own help (--enable_code_actions). The editors with fortls instructions on the fortls site are Visual Studio
Code, Atom, Sublime Text, Neovim, Vim (YouCompleteMe or LanguageClient-neovim), Emacs (LSP Mode or Eglot), Visual Studio 2017 and Kakoune.
Configuration
fortls reads .fortlsrc, .fortls.json or .fortls (JSON) from the project root, or the file named by -c/--config. Any --option of
the command line can be a key. The configuration used for this page:
{
"source_dirs": ["src", "app"],
"excl_paths": ["build", "doc", "doc-toml"],
"incl_suffixes": [".f90"],
"lowercase_intrinsics": true,
"hover_signature": true,
"max_line_length": 100,
"max_comment_line_length": 100,
"disable_autoupdate": true,
"enable_code_actions": false
}
The options from fortls --help, grouped as the help groups them:
| Option | Effect |
|---|---|
|
folders containing sources and folders to skip |
|
extra source extensions and excluded ones (defaults cover |
|
threads used to parse the workspace at start-up (default 4) |
|
incremental document synchronisation (beta) |
|
show attributes in a consistent order in hovers |
|
tune completion: no prefix filtering, no snippet placeholders, name only, or signature help instead of snippets |
|
complete intrinsics and keywords in lower case |
|
show argument signatures in hovers; language id used for hover blocks (default |
|
line-length diagnostics, or turn diagnostics off |
|
preprocessor handling: extensions parsed only for preprocessor commands, include folders, extra definitions as JSON |
|
leave type members out of document symbols |
|
experimental code actions |
|
fortls checks PyPI for newer versions and installs them by default; this switch stops that |
|
write a debug log in the project root |
The --disable_autoupdate option deserves attention in a managed environment: the default behaviour of a tool that updates itself from the
network is often not what a reproducible workspace wants, so pin the version and disable the update.
What it can do, shown by the debug commands
fortls has command-line tests for every request (fortls --debug_help lists them), which are useful to check a configuration without an editor.
They need --debug_rootpath so the workspace is loaded, and take 1-indexed --debug_line and --debug_char. All of the following are real
output on the documented heatflow copy (the repeated "Testing" header is trimmed).
Document symbols (the outline view):
$ fortls -c .fortls.json --debug_filepath src/heatflow_kernels.f90 --debug_symbols
line 9 symbol -> 2:heatflow_kernels parent = null
line 20 symbol -> 12:jacobi_sweep parent = heatflow_kernels
line 37 symbol -> 12:ftcs_sweep parent = heatflow_kernels
Hover on make_grid in app/main.f90 (line 22): the signature and the !> doc comment, which is why FORD-style comments pay twice, in
the generated site and in the editor:
FUNCTION make_grid(nx, ny, lx, ly, initial=initial) RESULT(g)
INTEGER(ip), INTENT(IN) :: nx
INTEGER(ip), INTENT(IN) :: ny
REAL(dp), INTENT(IN) :: lx
REAL(dp), INTENT(IN) :: ly
REAL(dp), INTENT(IN), OPTIONAL :: initial
TYPE(grid_t) :: g
-----
Create a grid covering `lx` by `ly`, optionally filled with `initial`.
Go to definition from the call make_grid (line 22, character 12) and from the type-bound call grid%apply_boundary (line 23):
URI = "file:///work/fordproj/src/heatflow_grid.f90"
Line = 27
Char = 12
URI = "file:///work/fordproj/src/heatflow_grid.f90"
Line = 17
Char = 18
The second result is the procedure :: apply_boundary => grid_apply_boundary binding line inside grid_t, not the body of the subroutine: fortls
resolves a type-bound call to the binding.
References to the variable residual (declared on line 13):
file:///work/fordproj/app/main.f90 (13, 15)
file:///work/fordproj/app/main.f90 (26, 49)
file:///work/fordproj/app/main.f90 (27, 69)
Rename residual to resid, a workspace-wide edit (shown as the diff the server returns):
File: "/work/fordproj/app/main.f90"
13, 13
- real(dp) :: residual
+ real(dp) :: resid
26, 26
- call solver%run(cfg%max_steps, cfg%tolerance, residual)
+ call solver%run(cfg%max_steps, cfg%tolerance, resid)
27, 27
- write (*, '(a,i0,a,es9.2)') 'steps=', solver%steps, ' residual=', residual
+ write (*, '(a,i0,a,es9.2)') 'steps=', solver%steps, ' residual=', resid
Note that the string ' residual=' on line 27 is correctly left alone: rename works on symbols, not text.
Completion after grid% lists the type-bound procedures of grid_t, with the generic write(formatted) and the specific binding
write_formatted (declared private in the type, but still offered by fortls):
3: apply_boundary -> SUBROUTINE apply_boundary(bc, value)
3: set_hot_spot -> SUBROUTINE set_hot_spot(temperature)
3: max_temperature -> REAL(dp) FUNCTION max_temperature()
2: write(formatted) -> SUBROUTINE write(formatted)(unit, iotype, v_list, iostat, iomsg)
3: write_formatted -> SUBROUTINE write_formatted(unit, iotype, v_list, iostat, iomsg)
Signature help on the second argument of make_grid reports the active parameter and the argument list:
Active param = 1
Active sig = 0
make_grid(nx, ny, lx, ly, initial=initial)
Workspace symbols for the query sweep find both kernels:
heatflow_kernels::37 symbol -> ftcs_sweep parent = src/heatflow_kernels.f90
heatflow_kernels::20 symbol -> jacobi_sweep parent = src/heatflow_kernels.f90
Diagnostics are deliberately limited. On a module with a missing use target and a duplicate declaration, fortls reports:
2:INFO "Module "missing_module" not found in project"
9:ERROR "Variable "x" declared twice in scope"
and on a line longer than the configured max_line_length, a WARNING that the line exceeds 100 characters. It did not report an unused local variable or a misspelled name, because fortls parses the source and
does not compile it: it has no type checker. Treat its diagnostics as editing aids and let the compiler (see
Compilers and Compiler Options) be the authority.
In the same way, an implementation request on a deferred binding (step in solver_t) returned "No result found" in this test, so
do not rely on that request for navigating from an abstract type to its extensions.
Limits to know about
-
fortls works from the source files and its own parser, with no knowledge of the compiler flags. Preprocessor-heavy code needs
--pp_suffixes,--include_dirsand--pp_defs, and the.F90extensions are by default parsed with the preprocessor. -
It does not read
compile_commands.json: a search of the installed fortls package for that file name finds nothing, so the server finds its sources only throughsource_dirs, the working root and the suffix options. Keepsource_dirsin sync with the build system. -
Modules from dependencies that live outside the workspace (for example stdlib built by fpm into
build/dependencies) are found only if their sources are inside asource_dirsentry; otherwise the INFO diagnostic "Module … not found in project" appears and completion for that module is missing. -
The language server cannot replace the compiler’s interface checks, and it does not know which Fortran standard you target.
Formatters and linters
The tools in this section are community tools, not part of any compiler. They are useful, but always review their diffs before committing, and keep a test run in the same change (see the modernisation workflow in Legacy Features and Modernization).
The input used below is a deliberately badly formatted copy of a smoothing routine (upper-case keywords, no spaces, no indentation):
MODULE Messy_Demo
USE heatflow_kinds, ONLY: dp
IMPLICIT NONE
CONTAINS
SUBROUTINE smooth(u,n,omega)
INTEGER,INTENT(IN)::n
REAL(dp),INTENT(INOUT)::u(n)
REAL(dp),INTENT(IN)::omega
INTEGER::i
DO i=2,n-1
IF(omega>0.0_dp)THEN
u(i)=u(i)+omega*(u(i-1)-2*u(i)+u(i+1))
ELSE
u(i)=u(i)
END IF
END DO
END SUBROUTINE smooth
END MODULE Messy_Demo
fprettify
fprettify (by Patrick Seewald, Python, pip install fprettify) reformats modern free-form Fortran: indentation,
whitespace around operators and commas, line length, and optionally the letter case of keywords. It reads configuration files named
.fprettify.rc in the home directory and in the parent directories of the file, or the file named with -c. Its options (from
fprettify --help) include -i/--indent (default 3), -l/--line-length (default 132), -w/--whitespace presets 0 to 4 (default 2) and the
individual --whitespace-comma, --whitespace-assignment and the other --whitespace-<kind> switches, --strict-indent, --enable-decl, --disable-indent, --disable-whitespace,
--enable-replacements, --c-relations, --case, --strip-comments, --disable-fypp, --disable-indent-mod, -d/--diff,
-s/--stdout, -S/--silent, -r/--recursive, -e/--exclude and -f/--fortran.
With the defaults (output to standard output, fprettify --stdout messy.f90):
MODULE Messy_Demo
USE heatflow_kinds, ONLY: dp
IMPLICIT NONE
CONTAINS
SUBROUTINE smooth(u, n, omega)
INTEGER, INTENT(IN)::n
REAL(dp), INTENT(INOUT)::u(n)
REAL(dp), INTENT(IN)::omega
INTEGER::i
DO i = 2, n - 1
IF (omega > 0.0_dp) THEN
u(i) = u(i) + omega*(u(i - 1) - 2*u(i) + u(i + 1))
ELSE
u(i) = u(i)
END IF
END DO
END SUBROUTINE smooth
END MODULE Messy_Demo
Two surprises in this real output, both documented behaviour:
-
The
ifinside thedois not indented. By default fprettify is "relaxed" about nested loops;--strict-indent("strictly impose indentation even for nested loops") indents every nesting level. -
The
::separators in declarations are left alone, because spacing of declarations is off unless--enable-declis given.
With a .fprettify.rc next to the file:
indent = 2
strict-indent = true
enable-decl = true
whitespace = 2
line-length = 100
and --case 1 1 1 1 on the command line (the four values are keywords, procedures and modules, operators, and constants; 0 leaves
as is, 1 lower-cases, 2 upper-cases), the output is:
module Messy_Demo
use heatflow_kinds, only: dp
implicit none
contains
subroutine smooth(u, n, omega)
integer, intent(IN) :: n
real(dp), intent(INOUT) :: u(n)
real(dp), intent(IN) :: omega
integer :: i
do i = 2, n - 1
if (omega > 0.0_dp) then
u(i) = u(i) + omega*(u(i - 1) - 2*u(i) + u(i + 1))
else
u(i) = u(i)
end if
end do
end subroutine smooth
end module Messy_Demo
The case option lower-cased the statement keywords but left the attribute arguments IN and INOUT and the name Messy_Demo as written. Running
fprettify a second time on the result changes nothing, so it is safe to run on save and in a pre-commit hook. In this test, a case line in the
.fprettify.rc was not honoured (the keywords stayed upper case, and some spellings of the value made fprettify print its usage text), while the same
setting on the command line worked, so pass --case on the command line.
Use fprettify -d file.f90 to see the diff instead of rewriting, and fprettify -r src -e build to process a tree. fprettify cannot
convert fixed-form source and may leave lines it cannot break; always compile afterwards.
findent
findent (a command-line program maintained by Willem Vermin, packaged in Debian as findent) indents Fortran and, unlike fprettify, handles
fixed form and can convert between the two. It reads standard input and writes standard output. On the same input, the default run changes
indentation but not spacing or case:
MODULE Messy_Demo
USE heatflow_kinds, ONLY: dp
IMPLICIT NONE
CONTAINS
SUBROUTINE smooth(u,n,omega)
INTEGER,INTENT(IN)::n
REAL(dp),INTENT(INOUT)::u(n)
REAL(dp),INTENT(IN)::omega
INTEGER::i
DO i=2,n-1
IF(omega>0.0_dp)THEN
u(i)=u(i)+omega*(u(i-1)-2*u(i)+u(i+1))
ELSE
u(i)=u(i)
END IF
END DO
END SUBROUTINE smooth
END MODULE Messy_Demo
Source: findent home page and manual
The useful part for older code is conversion. Given a fixed-form FORTRAN 77 routine, findent -ifixed -ofree produces free form and moves
labels to the left (-Rr also rewrites a bare END to end subroutine <name>):
SUBROUTINE AXPY(N,A,X,Y)
INTEGER N
DOUBLE PRECISION A,X(N),Y(N)
DO 10 I=1,N
Y(I)=Y(I)+A*X(I)
10 CONTINUE
RETURN
END
$ findent -ifixed -ofree -Rr < old.f
SUBROUTINE AXPY(N,A,X,Y)
INTEGER N
DOUBLE PRECISION A,X(N),Y(N)
DO 10 I=1,N
Y(I)=Y(I)+A*X(I)
10 CONTINUE
RETURN
end subroutine AXPY
The result passed gfortran -fsyntax-only -Wall in the container. The conversion keeps the obsolescent labelled do and the implicit typing: it
changes the form, not the style (that is the work of the steps in
Legacy Features and Modernization). Other options from
findent -h: -i<n> indent width, -ifree/-ifixed/-iauto input format, -ofree/-ofixed/-osame output format, -RR for upper-case
END SUBROUTINE name, --relabel to renumber labels, -q to guess whether a file is free or fixed, --ws_remred to collapse redundant white space, --openmp
to control indentation of OpenMP conditional lines, and --continuation=<c> for the continuation character. A comment line ! findentfix: <statement>
corrects the indentation when findent misjudges a construct. The Debian package also ships a wfindent wrapper script, and findent -lastindent is meant for use from Vim.
Fortitude and other linters
Fortitude (pip install fortitude-lint, a linter written in Rust, installed here as 0.9.2) is a static analyser in the style of ruff for Python.
It reports rule violations with codes, can apply fixes with --fix, and has output formats for CI (github, gitlab, junit, sarif, json
and more). On the messy module:
messy.f90:1:1: C131 module 'Messy_Demo' missing default accessibility statement
messy.f90:3:1: C003 'implicit none' missing 'external'
= help: Add `(external)` to 'implicit none'
fortitude: 1 files scanned.
Number of errors: 2
Both rules are exactly the habits recommended in Best Practices and Style: a private
default for modules and implicit none (type, external). Run over the heatflow sources with --statistics, the findings were
S241 bad-quote-string (43 occurrences, auto-fixable, the single versus double quote convention), C003 (12), C002 implicit typing in an interface (4),
C072 assumed-size character intent (2), C181 unchecked stat (2) and C011 missing default case (1). --target-std sets the
minimum standard (f95, f2003, f2008, f2018 default, f2023), --select/--ignore choose rules, and fortitude explain C003 documents a rule.
Fortitude is young and its rule set changes between releases, so pin its version in CI.
The most reliable linter remains the compiler itself. gfortran -fsyntax-only -std=f2023 -Wall -Wextra -pedantic -fimplicit-none (and the equivalents of
other compilers) checks types, interfaces and standard conformance, which no external linter does; see
Compilers and Compiler Options for the flag tables. Other community formatters and
linters exist; they were not installed for this page, so nothing is claimed about them.
compile_commands.json
compile_commands.json is the JSON compilation database format that was defined by the Clang project: an array of objects with
directory, file and either command or arguments, one object per source file compiled. Tools use it to learn the exact flags and include
paths used to compile each file. Fortran build systems can emit it too, which is useful for any tool that asks for it. The three build systems of this
guide produce it in these ways, all verified on the heatflow project.
fpm writes build/compile_commands.json on every fpm build:
{
"arguments": [
"gfortran", "-c", "././src/heatflow_boundary.f90",
"-fimplicit-none", "-Werror=implicit-interface", "-ffree-form",
"-I././include", "-J", "build/gfortran_DD609D2CDA6636FC",
"-Ibuild/gfortran_DD609D2CDA6636FC",
"-o", "build/gfortran_DD609D2CDA6636FC/heatflow/src_heatflow_boundary.f90.o"
],
"directory": "/work/hf",
"file": "././src/heatflow_boundary.f90"
}
The project had 13 entries (the 11 library modules, the app and the dependent files). The paths are relative to directory, and fpm build --help documents the
file under the --list option ("the build sequence is saved to <build-dir>/compile_commands.json").
CMake writes it with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON; the file is compile_commands.json in the build directory. Both the Ninja and the
Unix Makefiles generators produced it for the Fortran project. The first of the 14 entries with Ninja:
/usr/local/bin/gfortran -I/work/hf/bld/modules -I/work/hf/include -Jmodules -c /work/hf/src/heatflow_boundary.f90 -o CMakeFiles/heatflow.dir/src/heatflow_boundary.f90.o
Meson writes compile_commands.json in the build directory on every meson setup without an option (mb/compile_commands.json), with 12 entries for this
project. The first entry’s command:
gfortran -Ilibheatflow.a.p -I. -I.. -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Wextra -O0 -g -fPIC -Jlibheatflow.a.p -o libheatflow.a.p/src_heatflow_kinds.f90.o -c ../src/heatflow_kinds.f90
Three practical points. First, the compiler flags and paths are the build system’s, so the file is a way to reproduce a compile of one file (for
example -fsyntax-only with the same -I and -J options) and to feed analysis tools. Second, the module output directory (-J) and the include
directories matter because a Fortran file cannot be analysed without the .mod files of the modules it uses; the database tells a tool where they
are. Third, the language server above does not read this file (see "Limits to know about"), so do not expect a symlink of
compile_commands.json to the project root to improve fortls. Which of the other tools in your editor use it depends on the tool; check
its documentation, as this page does not claim any.
Community resources
-
The fortran-lang community site (https://fortran-lang.org/) hosts the tutorials, the style guide and best practices used throughout this guide, the compiler list and the learning resources, and it is the home of the packages mentioned above (fpm, stdlib, FORD, fortls, fprettify, test-drive).
-
The fortran-lang Discourse (https://fortran-lang.discourse.group/) is the discussion forum: questions about compilers and the standard, release announcements for the tools above, and the discussions behind proposals for the next standard.
-
The Fortran package index (https://fortran-lang.org/packages/) lists packages (libraries, tools and applications) that are usable with fpm, with their source repositories. Check licence and maintenance before depending on an entry, as for any package index.
-
The GitHub organisation
fortran-langhosts the sources and issue trackers of the tools in this page; reporting a formatter or language-server issue with a minimal file is usually the fastest way to get it fixed.
Where older material is outdated
Older Fortran material, such as Fortran 77 and early Fortran 90 tutorials, predates all of this tooling:
-
Documentation. Old projects document with free-form comments, Doxygen configuration with Fortran filters, or not at all. FORD understands modules, derived types and type-bound procedures, which Doxygen filters of the Fortran 77 era did not.
-
Editors. Older books assume a vendor IDE or a plain editor with column rulers for fixed form. A language server gives navigation, rename and hover in any LSP-capable editor for free-form code; fixed-form code is a weak case for fortls and best converted first (findent).
-
Formatting. Fixed-form tools such as column checkers do not apply to free form. fprettify and findent are the current community tools; the standard has no formatting rules.
-
Build metadata. Older makefiles carry compile flags in the makefile only. Modern builds can export a compilation database for tools.
-
Version baseline. Tool behaviour above is for the versions named at the top of the page; both fprettify’s
--strict-indentdefault and fortls’s--disable_autoupdatebehaviour are examples of settings that changed or may change, so read the--helpof the version you install.