Skip to content

Latest commit

 

History

History
186 lines (125 loc) · 8.66 KB

DEVELOPER.md

File metadata and controls

186 lines (125 loc) · 8.66 KB

Fourmolu — Developer notes

Contributing

Some things to keep in mind when making changes:

  • Make the minimal amount of changes
    • Avoid refactoring where possible, don't reformat untouched code
    • Since we continuously merge in changes from Ormolu, reducing the number of potential conflicts goes a long way towards maintainability of this project.
    • This includes behavior changes that drastically change how fourmolu formats Fourmolu's source code itself
  • Add a file to changelog.d/ when applicable (see changelog.d/README.md)

Running tests

The Fourmolu test suite contains the following types of tests:

  1. Tests inherited from Ormolu
  2. Unit tests specific for Fourmolu
  3. Integration tests

All of these tests can be run with cabal test or stack test.

To regenerate the printer test outputs (data/examples/**/*-four-out.hs), set ORMOLU_REGENERATE_EXAMPLES=1 in the environment before running test. Generally, this should not change any of the *-out.hs files, but it might change the *-four-out.hs files, if the setting in defaultPrinterOpts is different from the one in fourmolu.yaml

Running fourmolu

After building from source (see README.md), you can run Fourmolu with

scripts/run-fourmolu.sh --mode=inplace ...

This script automatically detects whether you built fourmolu with Stack or Cabal. If the auto-detection isn't working out, you can override it by setting export BUILD_TYPE={stack,cabal} in your environment.

This is automatically run on Fourmolu's source code in the pre-commit hooks (see the "Pre-commit hooks" section) and is checked in CI. If you're not using the pre-commit hooks, use the above command to manually style the files you changed (see .pre-commit-config.yaml for the files to exclude).

Pre-commit hooks

We highly recommend turning on pre-commit hooks to run checks every time you commit. To do so, install pre-commit and run pre-commit install in this directory.

This is optional, but is run in CI regardless.

Adding a new configuration option

Considering configurability is the raison d'être of Fourmolu, you're probably making a change that involves adding a new configuration option. Ideally, you've already opened an issue asking for thoughts on the new configuration. Assuming you've already done all that, here's a checklist to follow to ensure you've touched all the right places:

  1. Add the configuration option to config/FourmoluConfig/ConfigData.hs

    • Set sinceVersion to Nothing
    • If the option requires a custom data type, add one to allFieldTypes
  2. Regenerate files with config/generate.sh

  3. Make the required changes to change styling based on the configuration option

  4. Add a test case to Ormolu.Config.PrinterOptsSpec

    • Add a corresponding data/fourmolu/<label>/input.hs file
  5. Regenerate test outputs (see the "Running tests" section above)

  6. Add documentation in web/site/pages/config/<name>.md

  7. Add a file to changelog.d/ (see changelog.d/README.md)

Instant feedback with GHCID

We often want to immediately see how changes to Fourmolu's source code affect outputs. Try adding something like this to Ormolu.hs:

import qualified Data.Text.IO as T
import System.Directory (getHomeDirectory)
import System.FilePath ((</>))

main :: IO ()
main = do
  dir <- (</> "Desktop") <$> getHomeDirectory
  ormoluFile conf (dir </> "In.hs") >>= T.writeFile (dir </> "Out.hs")
  where
    conf =
      defaultConfig
        { cfgUnsafe = True,
          cfgPrinterOpts =
            defaultPrinterOpts
              { poCommaStyle = pure Trailing
              }
        }

Put some interesting code in In.hs. The contents of Out.hs can be kept up to date to reflect the result of running Fourmolu on it, by running:

ghcid -c 'cabal repl' -W -r --reload=$HOME/Desktop/In.hs

Release a new version

To release a new version, do the following workflow:

  1. Create a new branch

    1. Bump version in fourmolu.cabal

      • All version bumps should follow PvP
    2. Curate CHANGELOG.md (see changelog.d/README.md)

    3. Curate option order

      • Re-order the options in config/ConfigData.hs
        • Sort by popularity/importance (using your best judgement, without too much churn every release)
        • Regenerate with config/generate.sh
      • Ensure the PrinterOptsSpec.hs tests are also in the same order as the options
    4. Update any sinceVersion set to Nothing in ConfigData.hs

    5. Audit web/site/ docs

  2. Create PR as usual and merge into main

    1. In the check_sdist CI job, check the output of the stack sdist step for any warnings.
  3. Ensure your Hackage token is set in Settings > Secrets > Actions as HACKAGE_TOKEN_<github_username> (replace any non alphanumeric characters in username with _).

    • Generate a token from https://hackage.haskell.org/user/<hackage_username>/manage
  4. Go to the GitHub Actions page, click on the "Release" workflow, and click "Run workflow" on the main branch

  5. Publish the candidate: https://hackage.haskell.org/package/fourmolu/candidates

  6. Publish the GitHub release: https://github.com/fourmolu/fourmolu/releases

  7. If this is a new major version, update HLS to use it (example). It's rare that we'll be changing our API in a way that requires actual code changes.

  8. Publicize on Reddit (https://reddit.com/r/haskell) and Discourse (https://discourse.haskell.org)

Merging upstream

Fourmolu aims to continue merging upstream changes in Ormolu. Whenever Ormolu makes a new release (ideally within a week), the following steps should be run to merge the changes into Fourmolu.

  1. cd into your local copy of the Fourmolu repository
  2. Add Ormolu as an upstream remote: git remote add ormolu git@github.com:tweag/ormolu
  3. Check out a new branch: git switch -c merge-ormolu main
  4. Pull Ormolu's git history: git fetch ormolu --no-tags
  5. Find the commit corresponding to the new Ormolu version and merge it: git merge <commit> -m 'Merge ormolu-X.Y.Z'
  6. (Recommended) Switch to diff3 conflicts: git checkout --conflict=diff3. This provides more context that might be helpful for resolving conflicts. See docs.
  7. Resolve conflicts + finish merge: git merge --continue
  8. Run tests to ensure everything works well: stack test
  9. Make a PR and merge as usual
    1. MAKE SURE TO CREATE A MERGE COMMIT. Don't use the "Squash and merge" or "Rebase and merge" options.

Resolving conflicts

  • Conflicts at the following paths should be resolved by keeping the files DELETED (i.e. if there's a "deleted by us" conflict, use git rm to avoid adding the file to our repo):

    • **/.envrc
    • **/.ormolu
    • .github/workflows/binaries.yml
    • CONTRIBUTING.md
    • DESIGN.md
    • flake.lock
    • flake.nix
    • nix/
    • ormolu-live/
    • weeder.dhall
  • Conflicts at the following paths should be resolved by throwing out Ormolu's changes and keeping our changes (i.e. if there's a conflict, use git checkout --ours):

    • stack.yaml
    • .github/workflows/ci.yml
  • The state of the following paths should be the same as they are in Ormolu (i.e. if there's a conflict, use git checkout --theirs)

    • expected-failures/
  • If any of the default.nix files are changed, manually verify that all end-to-end tests are accounted for. After doing so, git rm each of them.

    • For example, ./region-tests/ is one directory of tests, which is captured in the Ormolu.Integration.RegionSpec test suite, where every test in region-tests/default.nix has been ported into the Haskell test suite.
  • Any Ormolu additions to CHANGELOG.md should NOT be kept, but instead be added to a new file in changelog.d/ (e.g. named ormolu-X.Y.Z). See changelog.d/README.md for more details.

  • Be careful when editing fourmolu.cabal to only change shared things (e.g. tested-with) and not Fourmolu things (e.g. name or version).

Update tests

  • Regenerate test files (see the "Running tests" section above)
  • Remove any redundant Fourmolu output files
    ./scripts/clean_redundant_examples.py

HLint

Ormolu isn't HLint-clean, and making Fourmolu HLint-clean would increase the diff with Ormolu and make merge conflicts more likely, so we shouldn't make unnecessary changes to solve HLint warnings.

If you're using HLS you may wish to disable HLint on this codebase entirely. In VSCode, for example, add "haskell.plugin.hlint.diagnosticsOn": false to fourmolu/.vscode/settings.json.