Skip to content

Commit

Permalink
bears/haskell: Add GhcModBear
Browse files Browse the repository at this point in the history
Add ghc-mod feature to coala for syntax checking for
haskell files (.hs)

Closes #297
  • Loading branch information
vijeth-aradhya committed Jan 15, 2017
1 parent 75ae1c9 commit 4ac8fde
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .ci/deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ if [[ -z "$(which hlint)" ]]; then
sudo dpkg -i $hlint_deb
fi

# cabal update to 1.22.9.0 and install ghc-mod 5.6.0
cabal update && cabal install cabal-install-1.22.9.0
cabal install ghc-mod

# NPM commands
sudo rm -rf $(which alex) # Delete ghc-alex as it clashes with npm deps
npm install
Expand Down
28 changes: 28 additions & 0 deletions bears/haskell/GhcModBear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from coalib.bearlib.abstractions.Linter import linter
from dependency_management.requirements.CabalRequirement import (
CabalRequirement)


@linter(executable='ghc-mod',
output_format='regex',
output_regex=r'.+:(?P<line>\d+):(?P<column>\d+):'
r'(?P<message>.+)')
class GhcModBear:
"""
Syntax checking with ``ghc`` for Haskell files.
See <https://hackage.haskell.org/package/ghc-mod> for more information!
"""

LANGUAGES = {'Haskell'}
REQUIREMENTS = {CabalRequirement(package='ghc-mod', version='5.6.0')}
AUTHORS = {'The coala developers'}
AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
LICENSE = 'AGPL-3.0'
ASCIINEMA_URL = 'https://asciinema.org/a/98873'
CAN_DETECT = {'Syntax'}

@staticmethod
def create_arguments(filename, file, config_file):
# -b '. ' is the argument given for ghc-mod for seperation of messages
return '-b', '. ', 'check', filename
99 changes: 99 additions & 0 deletions tests/haskell/GhcModBearTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from queue import Queue

from bears.haskell.GhcModBear import GhcModBear
from coalib.testing.LocalBearTestHelper import LocalBearTestHelper
from coalib.testing.LocalBearTestHelper import verify_local_bear
from coalib.testing.BearTestHelper import generate_skip_decorator
from coalib.settings.Section import Section
from coalib.settings.Setting import Setting


# A simple hello world program!
hello_world_file = """
main :: IO()
main = putStrLn "Hello World!"
""".splitlines()

# No instance for (Num String) in the first argument of ‘putStrLn’
not_string_3 = """
main :: IO()
main = putStrLn 3
""".splitlines()

# Takes a file and prints the number of lines
count_lines = """
main :: IO()
main = interact lineC
where lineC input = show (length(lines input))
""".splitlines()

# Parse Error (mismatched brackets)
count_lines_bracket_missing = """
main :: IO()
main = interact lineC
where lineC input = show (length(lines input)
""".splitlines()

# Prints "asd" which is the last but one element in the list
last_but_one_list = """
lastButOne :: [a] -> a
lastButOne xs | (length xs >= 2) = xs !! ((length xs) - 2)
| otherwise = error "Not possible"
main :: IO()
main = do
print(lastButOne(["asd", "ASd"]))
""".splitlines()

# Couldn't match expected type ‘[a]’ with actual type ‘a’
last_but_one_list_type_error = """
lastButOne :: a -> a
lastButOne xs | (length xs >= 2) = xs !! ((length xs) - 2)
| otherwise = error "Not possible"
main :: IO()
main = do
print(lastButOne(["asd", "ASd"]))
""".splitlines()


@generate_skip_decorator(GhcModBear)
class GhcModBearTest(LocalBearTestHelper):

def setUp(self):
self.section = Section('name')
self.uut = GhcModBear(self.section, Queue())

def test_valid(self):
self.check_validity(self.uut, hello_world_file, valid=True,
tempfile_kwargs={'suffix': '.hs'})
self.check_validity(self.uut, count_lines, valid=True,
tempfile_kwargs={'suffix': '.hs'})
self.check_validity(self.uut, last_but_one_list,
valid=True,
tempfile_kwargs={'suffix': '.hs'})

def test_invalid(self):
results = self.check_validity(self.uut, not_string_3,
valid=False,
tempfile_kwargs={'suffix': '.hs'})
self.assertEqual(len(results), 1, str(results))
# to check for the seperator
self.assertEqual(len(str(results[0].message).split('.')), 4,
str(results))
self.assertIn('No instance for (Num String) arising from the literal ',
results[0].message)
results = self.check_validity(self.uut,
count_lines_bracket_missing,
valid=False,
tempfile_kwargs={'suffix': '.hs'})
self.assertEqual(len(results), 1, str(results))
self.assertIn('parse error (possibly incorrect indentation '
'or mismatched brackets)', results[0].message)
results = self.check_validity(self.uut,
last_but_one_list_type_error,
valid=False,
tempfile_kwargs={'suffix': '.hs'})
self.assertEqual(len(results), 1, str(results))
self.assertIn("Couldn't match expected type",
results[0].message)

0 comments on commit 4ac8fde

Please sign in to comment.