New Fortitude release (0.6.0): a Fortran linter

I’m really excited to announce a new version of fortitude! Fortitude is a Fortran linter, heavily inspired by the Python linter ruff. We’ve added a bunch of new rules, but more excitingly, we’ve started adding auto-fixes – so far, mostly for style issues, but we’re continually adding new rules and fixes.

Why should you use fortitude?

  • it’s fast – running fortitude check --fix on a large, popular scientific package fixed 23,376 issues across 473 files in 1.4s! Most
  • it catches bugs – rules like assumed-size-character-intent and implicit-external-procedures help catch real bugs in code
  • it helps you follow community best practices
  • it’s opinionated but customisable – don’t like double-precision? Turn it off in your fpm.toml file
  • it integrates with your CI – use --output-format=github to get automatic annotations on your PRs
  • it’s continuously expanding – this release sees us increase to 34 rules, with another 30 or so in the pipeline!

Planned features include per-file and per-line ignores, an opinionated formatter, and editor integration – warnings as fast as you can type!

We’d love to hear your feedback, especially on the new rules in preview – contributors even more welcome! :smiley:

New Features

  • Adds --preview flag. Going forward, many new features will be hidden behind this prior to a stable release.
  • Adds autofixes.
    • A subset of rules can now be fixed automatically by passing --fix or --unsafe-fix (mostly style-based rules)
    • Fixable rules are highlighted in the output
    • See the new rules page for an overview of which rules are now fixable
  • Adds rules to the modules category for checking accessibility in modules (both in preview mode)
    • missing-accessibility-statement requires you to explicitly declare the default accessibility to private or public
    • default-public-accessibility requires you to set the default accessibility to private
  • Adds rules to the style category:
    • missing-double-colon requires variable declarations to include ::, e.g. integer :: i instead of integer i.
    • incorrect-space-before-comment requires at least two spaces before an inline comment.
  • Adds rules to the typing category:
    • external-procedure warns for the use of procedures within implicit interfaces using the external declaration.
    • implicit-external-procedures warns when a bare implicit none is used instead of implicit none (type, external), which forbids implicit typing when calling external procedures.
  • Adds the ‘obsolescent’ rule category for catching outdated Fortran features, with three new rules:
    • common-block
    • statement-function
    • entry-statement
  • Adds an optional progress bar – useful when running on very large projects or on slower hardware.
  • The no-real-suffix rule has been upgraded, and no longer complains in cases where no precision would be lost, e.g:
use, intrinsic :: iso_fortran_env, only: dp => real64
real(dp), parameter :: x = 3.141592653589  ! Error, RHS is single precision, precision lost in assignment
real(dp), parameter :: x = 3.141592653589_dp  ! Okay
real(dp), parameter :: y = 1  ! Okay, no precision lost

Bug-fixes

  • trailing-whitespace and line-too-long can handle multibyte UTF-8 characters in strings and comments
  • Correctly report number of files scanned on completion

Breaking changes

  • The rule external-function has been renamed procedure-not-in-module to avoid confusion with the new external-procedure rule.
12 Likes

Thanks for all your work. Looking at the last code block, I suggest that for

real(dp) :: y
y = 1

that Fortitude warn about casting an integer to a real and recommend instead

y = real(1, kind=dp)

since that makes the intention clear. Whether to turn on such a warning by default is a matter of taste.

1 Like

Whoops, that was a error in the changelog I wrote, and it looks like @zedthree copied it here! We aren’t yet catching implicit casts, and I meant to write:

real(dp), parameter :: y = 1.0  ! Okay, no precision lost

Previously it would require every floating point literal to have a kind suffix, even in cases like this where clearly there isn’t an issue. We’re still quite strict in other situations though, and we aren’t yet analysing arithmetic to recognise that things like 0.0 * some_real and 0.0_dp * some_real are the same.

A rule for implicit casts is a good idea though!

Thanks for all your work! I’m already using this to great success in my projects.

I had a question about integration with pre-commit. Currently I’m using it via something like

- repo: local
  hooks:
  - id: fortitude-lint
    name: Run Fortitude linter
    entry: fortitude check --fix --progress-bar=ascii --output-format=concise --line-length=132
    language: system
    pass_filenames: yes
    always_run: true
    files: \.[fF]\d90*$

which does work, but has the side effect that it runs on all files - including those in the build directory. This is a bit problematic if using other packages via FPM (such as stdlib). It’s easy enough to fix by deleting build, but I was wondering if there was a better way already setup?

1 Like

Thanks @RJaBi, glad that you’re finding it useful!

At the moment, your only real option is to run fortitude check <directory> to just run it over files in a given directory. In the next release, we’ll have the ability to exclude files, directories, and patterns, so you’ll be able to put this in a configuration file and always ignore build. I’d also like the ability to automatically ignore anything in .gitignore, so this would be sensible out of the box.

1 Like