HLS's fourmolu plugin does not respect `import-grouping: by-scope-then-qualified`
Your environment
Which OS do you use?
$ uname -a
Darwin *** 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6020 arm64
Which version of GHC do you use and how did you install it?
$ which ghc
/Users/bf/.ghcup/bin/ghc
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.10.2
How is your project built (alternative: link to the project)? link
Which LSP client (editor/plugin) do you use? Zed with the Haskell extension Which version of HLS do you use and how did you install it?
$ haskell-language-server-wrapper --version
haskell-language-server version: 2.11.0.0 (GHC: 9.10.2) (PATH: /Users/bf/.ghcup/hls/2.11.0.0/lib/haskell-language-server-2.11.0.0/bin/haskell-language-server-wrapper)
Have you configured HLS in any way (especially: a hie.yaml file)?
"hls": {
"initialization_options": {
"haskell": {
"formattingProvider": "fourmolu",
"plugin": {
"fourmolu": {
"enabled": true,
"config": {
"external": true
}
},
"hlint": {
"enabled": true
}
}
}
}
}
Steps to reproduce
Let config.yaml contain import-grouping: by-scope-then-qualified. Format via HLS (e.g. on save). Format with fourmolu binary. There is a diff.
Expected behaviour
No diff.
Actual behaviour
There is a diff.
Debug information
I can replicate this, but I'm not sure what the cause could be. Is there another plugin that's sorting the imports after fourmolu?
I can reproduce this with VSCode 1.105.1 / vscode-haskell 2.6.1 and Emacs 30.2 / eglot 1.17.30, so it's definitely a HLS issue, not editor-specific.
Specifically, it seems to sort the imports only by-scope. Maybe there is something wrong with the word splitting, i.e. an early match or something of the sort?
Looking at the code in Ide.Plugin.Fourmolu, looks like adding some debug printing for the resolved configuration might help in investigating this.
Looks like @georgefst is the code owner for the fourmolu plugin. Can I help with triage in some way?
Could you paste here the code you're formatting, and the diff between the output when run in and outside of HLS. Would also be good to know the list of plugin versions in your HLS, and the versions of HLS and the Fourmolu binary. Also whether this happens only with "external": true.
@brandonchinn178 Any ideas? I don't think I even reviewed this new setting so I'm a bit out of the loop. Anyway, we don't have special handling for particular settings in the plugin, so it should all just work generically...
Yeah, I'm not sure why this would be special. cc @michivi, who implemented this
I do see a new local-modules option in fourmolu.yaml, that we should double check is being respected (since it's not a normal PrinterOpts field), but I don't think that would affect this
I believe the difference is due to the --no-cabal flag that HLS transmits to the fourmolu executable, which basically tells Fourmolu to not look into the cabal files to extract various pieces of information, including the local modules. Without that, we can only group by qualified. I don't have the history as to why HLS pushes this flag, I can only see that it is used for recent versions of Fourmolu (581686c4729f9ce2dd6ca42a7842701ffa165d9a). @georgefst I think you are the one introducing the use of that flag? Do you remember why it was introduced?
Aha, good catch.
FWIW, I think "recent" there was just referring to versions which supported --no-cabal.
The idea is that we don't need to parse Cabal files when using Fourmolu from inside HLS because HLS already gives us all the information that Ormolu's Cabal file parsing was built to extract, primarily language extensions. I don't know whether that's still true with import-grouping, and I can't remember exactly what, if anything, would go wrong if we didn't pass --no-cabal. It's possible that passing it might even give us a cheap fix for #3454...
Note that this affects both the built-in Fourmolu handler and the CLI handler. Maybe this is because on this line we pass Nothing as the CabalInfo parameter?
I don't know whether that's still true with import-grouping, and I can't remember exactly what, if anything, would go wrong if we didn't pass --no-cabal.
Naively just removing that flag gives Could not find a .cabal file for /opt/homebrew/bin/fourmolu (when using external fourmolu), so there's something funny with how we're calling it, either with the cwd or args.
Looks like we could also use the -m flag to fourmolu to pass in modules local to this package. However, I wonder if it's sustainable in general to keep adding things to pass into Fourmolu instead of just letting it figure things out itself. OTOH the latter approach can lead to HLS and Fourmolu having different opinions about what configuration is in effect.