Skip to content

Neotest adapter for Haskell (cabal or stack) with support for Sydtest, Hspec and Tasty

License

Notifications You must be signed in to change notification settings

mrcjkb/neotest-haskell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation


neotest-haskell

Report Bug

A neotest adapter for Haskell.

πŸ¦₯

Neovim Lua Haskell Nix

GPL2 License Issues Build Status LuaRocks

All Contributors

Quick links

Features

  • Supports Cabal (single/multi-package) projects.
  • Supports Stack (single/multi-package) projects.
  • Parses Hspec and Sydtest --match filters for the cursor's position using tree-sitter.
  • Parses Tasty --pattern filters for the cursor's position using tree-sitter.
  • Parses test results and displays error messages as diagnostics.
neotest.mp4

Installation

rocks.nvim

:Rocks install neotest-haskell

rocks.nvim will install all dependencies if not already installed (including tree-sitter-haskell).

Other plugin managers

See also: neotest installation instructions.

  • Requires the tree-sitter parser for haskell to be installed.

The following example uses lazy.nvim:

{
  'nvim-neotest/neotest',
  dependencies = {
    -- ...,
    'mrcjkb/neotest-haskell',
    'nvim-lua/plenary.nvim',
  }
}

Configuration

Make sure the Haskell parser for tree-sitter is installed, you can do so via nvim-treesitter like so:

require('nvim-treesitter.configs').setup {
  ensure_installed = {
    'haskell',
    --...,
  },
}

Add neotest-haskell to your neotest adapters:

require('neotest').setup {
  -- ...,
  adapters = {
    -- ...,
    require('neotest-haskell')
  },
}

You can also pass a config to the setup. The following are the defaults:

require('neotest').setup {
  adapters = {
    require('neotest-haskell') {
      -- Default: Use stack if possible and then try cabal
      build_tools = { 'stack', 'cabal' },
      -- Default: Check for tasty first and then try hspec
      frameworks = { 'tasty', 'hspec', 'sydtest' },
    },
  },
}

Note

If you were to use build_tools = { 'cabal', 'stack' }, then cabal will almost always be chosen, because almost all stack projects can be built with cabal.

Alternately, you can pair each test framework with a list of modules, used to identify the respective framework in a test file:

require('neotest').setup {
  adapters = {
    require('neotest-haskell') {
      frameworks = {
        { framework = 'tasty', modules = { 'Test.Tasty', 'MyTestModule' }, },
        'hspec',
        'sydtest',
      },
    },
  },
}

This can be useful if you have test files that do not import one of the default modules used for framework identification:

  • tasty: modules = { 'Test.Tasty' }
  • hspec: modules = { 'Test.Hspec' }
  • sydtest: modules = { 'Test.Syd' }

Advanced configuration

This plugin uses tree-sitter queries in files that match <runtimepath>/queries/haskell/<framework>-positions.scm

For example, to add position queries for this plugin for tasty, without having to fork this plugin, you can add them to $XDG_CONFIG_HOME/nvim/after/queries/haskell/tasty-positions.scm.

Note

Examples

module FixtureSpec ( spec ) where
import Test.Hspec
import Test.Hspec.QuickCheck
import Control.Exception ( evaluate )

spec :: Spec
spec = describe "Prelude.head" $ do
  it "returns the first element of a list" $ head [23 ..] `shouldBe` (23 :: Int)

  prop "returns the first element of an *arbitrary* list" $ \x xs ->
    head (x : xs) `shouldBe` (x :: Int)

  describe "Empty list" $
    it "throws an exception if used with an empty list"
      $             evaluate (head [])
      `shouldThrow` anyException

In the above listing, calling :lua require('neotest').run.run() with the cursor on the line...

  describe "Empty list" $

...will run the tests with the following Cabal command:

# Assuming a Cabal package called "my_package"
cabal test my_package --test-option -m --test-option "/Prelude.head/Empty list/"

...or with the following Stack command:

# Assuming a Stack package called "my_package"
stack test my_package --ta "--match \"/Prelude.head/Empty list/\""

...which will run the "throws an exception if used with an empty list" test.

Calling :lua require('neotest').run.run() with the cursor on the line...

spec = describe "Prelude.head" $ do

...will run the tests with the following Cabal command:

# Assuming a Cabal package called "my_package"
cabal test my_package --test-option -m --test-option "/Prelude.head/"

...or with the following Stack command:

# Assuming a Stack package called "my_package"
stack test my_package --ta "--match \"/Prelude.head/\""

...which will run all tests in the module.

TODO

See issues.

Troubleshooting

To run a health check, run :checkhealth neotest-haskell in Neovim.

Limitations

  • To run sydtest tests of type 'file', sydtest >= 0.13.0.4 is required, if the file has more than one top-level namespace (describe, context, ..).

Recommendations

Here are some other plugins I recommend for Haskell development:

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Perigord
Perigord

πŸ’»
Sebastian Witte
Sebastian Witte

πŸ’» πŸš‡ πŸ“–
Andy Bell
Andy Bell

πŸ’»
Tom Sydney Kerckhove
Tom Sydney Kerckhove

πŸ§‘β€πŸ«
Nadeem Bitar
Nadeem Bitar

πŸ›
Mango The Fourth
Mango The Fourth

πŸ›
HΓ©cate Moonlight
HΓ©cate Moonlight

πŸ›
Amaan Qureshi
Amaan Qureshi

πŸ’»
Brad Sherman
Brad Sherman

πŸ’»

This project follows the all-contributors specification. Contributions of any kind welcome!