packages feed

difftodo (empty) → 0.1.0

raw patch · 11 files changed

+803/−0 lines, 11 filesdep +basedep +bytestringdep +diff-parsesetup-changed

Dependencies added: base, bytestring, diff-parse, difftodo, highlighting-kate, optparse-applicative, protolude, tasty, tasty-hunit, text

Files

+ LICENSE view
@@ -0,0 +1,163 @@+Apache License++Version 2.0, January 2004++http://www.apache.org/licenses/++TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION++1. Definitions.++"License" shall mean the terms and conditions for use, reproduction, and+distribution as defined by Sections 1 through 9 of this document.++"Licensor" shall mean the copyright owner or entity authorized by the+copyright owner that is granting the License.++"Legal Entity" shall mean the union of the acting entity and all other+entities that control, are controlled by, or are under common control with+that entity. For the purposes of this definition, "control" means (i) the+power, direct or indirect, to cause the direction or management of such+entity, whether by contract or otherwise, or (ii) ownership of fifty percent+(50%) or more of the outstanding shares, or (iii) beneficial ownership of such+entity.++"You" (or "Your") shall mean an individual or Legal Entity exercising+permissions granted by this License.++"Source" form shall mean the preferred form for making modifications,+including but not limited to software source code, documentation source, and+configuration files.++"Object" form shall mean any form resulting from mechanical transformation or+translation of a Source form, including but not limited to compiled object+code, generated documentation, and conversions to other media types.++"Work" shall mean the work of authorship, whether in Source or Object form,+made available under the License, as indicated by a copyright notice that is+included in or attached to the work (an example is provided in the Appendix+below).++"Derivative Works" shall mean any work, whether in Source or Object form, that+is based on (or derived from) the Work and for which the editorial revisions,+annotations, elaborations, or other modifications represent, as a whole, an+original work of authorship. For the purposes of this License, Derivative+Works shall not include works that remain separable from, or merely link (or+bind by name) to the interfaces of, the Work and Derivative Works thereof.++"Contribution" shall mean any work of authorship, including the original+version of the Work and any modifications or additions to that Work or+Derivative Works thereof, that is intentionally submitted to Licensor for+inclusion in the Work by the copyright owner or by an individual or Legal+Entity authorized to submit on behalf of the copyright owner. For the purposes+of this definition, "submitted" means any form of electronic, verbal, or+written communication sent to the Licensor or its representatives, including+but not limited to communication on electronic mailing lists, source code+control systems, and issue tracking systems that are managed by, or on behalf+of, the Licensor for the purpose of discussing and improving the Work, but+excluding communication that is conspicuously marked or otherwise designated+in writing by the copyright owner as "Not a Contribution."++"Contributor" shall mean Licensor and any individual or Legal Entity on behalf+of whom a Contribution has been received by Licensor and subsequently+incorporated within the Work.++2. Grant of Copyright License. Subject to the terms and conditions of this+License, each Contributor hereby grants to You a perpetual, worldwide,+non-exclusive, no-charge, royalty-free, irrevocable copyright license to+reproduce, prepare Derivative Works of, publicly display, publicly perform,+sublicense, and distribute the Work and such Derivative Works in Source or+Object form.++3. Grant of Patent License. Subject to the terms and conditions of this+License, each Contributor hereby grants to You a perpetual, worldwide,+non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this+section) patent license to make, have made, use, offer to sell, sell, import,+and otherwise transfer the Work, where such license applies only to those+patent claims licensable by such Contributor that are necessarily infringed by+their Contribution(s) alone or by combination of their Contribution(s) with+the Work to which such Contribution(s) was submitted. If You institute patent+litigation against any entity (including a cross-claim or counterclaim in a+lawsuit) alleging that the Work or a Contribution incorporated within the Work+constitutes direct or contributory patent infringement, then any patent+licenses granted to You under this License for that Work shall terminate as of+the date such litigation is filed.++4. Redistribution. You may reproduce and distribute copies of the Work or+Derivative Works thereof in any medium, with or without modifications, and in+Source or Object form, provided that You meet the following conditions:++You must give any other recipients of the Work or Derivative Works a copy of+this License; and++You must cause any modified files to carry prominent notices stating that You+changed the files; and++You must retain, in the Source form of any Derivative Works that You+distribute, all copyright, patent, trademark, and attribution notices from the+Source form of the Work, excluding those notices that do not pertain to any+part of the Derivative Works; and++If the Work includes a "NOTICE" text file as part of its distribution, then+any Derivative Works that You distribute must include a readable copy of the+attribution notices contained within such NOTICE file, excluding those notices+that do not pertain to any part of the Derivative Works, in at least one of+the following places: within a NOTICE text file distributed as part of the+Derivative Works; within the Source form or documentation, if provided along+with the Derivative Works; or, within a display generated by the Derivative+Works, if and wherever such third-party notices normally appear. The contents+of the NOTICE file are for informational purposes only and do not modify the+License. You may add Your own attribution notices within Derivative Works that+You distribute, alongside or as an addendum to the NOTICE text from the Work,+provided that such additional attribution notices cannot be construed as+modifying the License.++You may add Your own copyright statement to Your modifications and may provide+additional or different license terms and conditions for use, reproduction, or+distribution of Your modifications, or for any such Derivative Works as a+whole, provided Your use, reproduction, and distribution of the Work otherwise+complies with the conditions stated in this License.++5. Submission of Contributions. Unless You explicitly state otherwise, any+Contribution intentionally submitted for inclusion in the Work by You to the+Licensor shall be under the terms and conditions of this License, without any+additional terms or conditions. Notwithstanding the above, nothing herein+shall supersede or modify the terms of any separate license agreement you may+have executed with Licensor regarding such Contributions.++6. Trademarks. This License does not grant permission to use the trade names,+trademarks, service marks, or product names of the Licensor, except as+required for reasonable and customary use in describing the origin of the Work+and reproducing the content of the NOTICE file.++7. Disclaimer of Warranty. Unless required by applicable law or agreed to in+writing, Licensor provides the Work (and each Contributor provides its+Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY+KIND, either express or implied, including, without limitation, any warranties+or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+PARTICULAR PURPOSE. You are solely responsible for determining the+appropriateness of using or redistributing the Work and assume any risks+associated with Your exercise of permissions under this License.++8. Limitation of Liability. In no event and under no legal theory, whether in+tort (including negligence), contract, or otherwise, unless required by+applicable law (such as deliberate and grossly negligent acts) or agreed to in+writing, shall any Contributor be liable to You for damages, including any+direct, indirect, special, incidental, or consequential damages of any+character arising as a result of this License or out of the use or inability+to use the Work (including but not limited to damages for loss of goodwill,+work stoppage, computer failure or malfunction, or any and all other+commercial damages or losses), even if such Contributor has been advised of+the possibility of such damages.++9. Accepting Warranty or Additional Liability. While redistributing the Work+or Derivative Works thereof, You may choose to offer, and charge a fee for,+acceptance of support, warranty, indemnity, or other liability obligations+and/or rights consistent with this License. However, in accepting such+obligations, You may act only on Your own behalf and on Your sole+responsibility, not on behalf of any other Contributor, and only if You agree+to indemnify, defend, and hold each Contributor harmless for any liability+incurred by, or claims asserted against, such Contributor by reason of your+accepting any such warranty or additional liability.++END OF TERMS AND CONDITIONS
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ all-todos/Main.hs view
@@ -0,0 +1,59 @@+-- | Get todos from source code+--+-- e.g.+--   all-todos foo.py++module Main (main) where++import Protolude++import Data.Text.IO (readFile)+import GHC.IO (FilePath)+import Options.Applicative+  ( ParserInfo+  , argument+  , execParser+  , fullDesc+  , header+  , helper+  , info+  , metavar+  , progDesc+  , str+  )++import qualified Fixme+import Fixme.Comment (Comment, languageForFile)+++options :: ParserInfo [FilePath]+options =+  info (helper <*> parser) description+  where+    parser = many (argument str (metavar "FILES..."))++    description = mconcat+      [ fullDesc+      , progDesc "Find all todos from source code"+      , header "all-todos - Get all todos in source code"+      ]+++readComments :: FilePath -> IO (Maybe [Comment])+readComments filename =+  case languageForFile (toS filename) of+    Nothing -> pure Nothing+    Just language -> do+      contents <- readFile filename+      pure (Just $ Fixme.parseComments language contents)+++main :: IO ()+main = do+  files <- execParser options+  forM_ files $ \filename -> do+    comments' <- readComments filename+    case comments' of+      Nothing -> pure ()+      Just comments -> do+        mapM_ (putStrLn . Fixme.formatTodo) (concatMap Fixme.getTodos comments)
+ difftodo.cabal view
@@ -0,0 +1,72 @@+-- This file has been generated from package.yaml by hpack version 0.13.0.+--+-- see: https://github.com/sol/hpack++name:           difftodo+version:        0.1.0+synopsis:       Generate todo lists from source code+category:       Development+homepage:       https://github.com/jml/difftodo#readme+bug-reports:    https://github.com/jml/difftodo/issues+maintainer:     Jonathan M. Lange <jml@mumak.net>+license:        Apache+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10++source-repository head+  type: git+  location: https://github.com/jml/difftodo++library+  hs-source-dirs:+      src+  default-extensions: NoImplicitPrelude OverloadedStrings NamedFieldPuns RecordWildCards+  ghc-options: -Wall+  build-depends:+      base >= 4.9 && < 5+    , protolude >= 0.1.5+    , text+    , diff-parse+    , highlighting-kate+  exposed-modules:+      Fixme+      Fixme.Comment+      Fixme.Diff+      Fixme.Todo+  default-language: Haskell2010++executable all-todos+  main-is: Main.hs+  hs-source-dirs:+      all-todos+  default-extensions: NoImplicitPrelude OverloadedStrings NamedFieldPuns RecordWildCards+  ghc-options: -Wall+  build-depends:+      base >= 4.9 && < 5+    , protolude >= 0.1.5+    , text+    , difftodo+    , optparse-applicative+  default-language: Haskell2010++test-suite fixme-tests+  type: exitcode-stdio-1.0+  main-is: Main.hs+  hs-source-dirs:+      tests+  default-extensions: NoImplicitPrelude OverloadedStrings NamedFieldPuns RecordWildCards+  ghc-options: -Wall+  build-depends:+      base >= 4.9 && < 5+    , protolude >= 0.1.5+    , text+    , bytestring+    , highlighting-kate+    , difftodo+    , tasty+    , tasty-hunit+  other-modules:+      Comment+      Diff+  default-language: Haskell2010
+ src/Fixme.hs view
@@ -0,0 +1,17 @@+-- | Routines for getting todos from source code+--+-- No backwards compatibility is promised for any code in this library.++module Fixme+  ( -- | Get comments from code or from diffs+    Comment.parseComments+  , Diff.newCommentsFromDiff+    -- | Turn comments into todos+  , Todo.getTodos+    -- | Format todos+  , Todo.formatTodo+  ) where++import qualified Fixme.Comment as Comment+import qualified Fixme.Diff as Diff+import qualified Fixme.Todo as Todo
+ src/Fixme/Comment.hs view
@@ -0,0 +1,111 @@+module Fixme.Comment+  ( -- * Understand comments+    Comment+  , Located+  , parseComments+  , commentText+  , startLine+  , endLine+    -- ** Exposed for testing+  , newComment+  , parseComments'++    -- * Understand programming languages+  , Language+  , languageForFile+    -- ** Exposed for testing+  , highlightCode+  ) where++import Protolude++import qualified Data.Text as Text+import Text.Highlighting.Kate+  ( SourceLine+  , Token+  , TokenType(..)+  , highlightAs+  , languagesByFilename+  )+++-- | Given some source code, return a list of comments.+parseComments :: Language -> Text -> [Comment]+parseComments language = parseComments' . highlightCode language++-- | Given a consecutive sequence of lexed lines of source, return a list of+-- all the comments found, along with the line number on which the comment+-- starts.+parseComments' :: [SourceLine] -> [Comment]+parseComments' =+  coalesce appendComment . mapMaybe getComment . locateTokens++  where+    coalesce :: (a -> a -> Maybe a) -> [a] -> [a]+    coalesce maybeAppend (x:y:ys) =+      case x `maybeAppend` y of+        Nothing -> x:(coalesce maybeAppend (y:ys))+        Just z -> (coalesce maybeAppend (z:ys))+    coalesce _ xs = xs+++-- | A thing that is located somewhere in a text file.+data Located a = Located { startLine :: Int+                         , value :: a+                         } deriving (Eq, Show)++type LocatedToken = Located Token++locateTokens :: [SourceLine] -> [LocatedToken]+locateTokens lines = [Located i x | (i, xs) <- zip [0..] lines, x <- xs]+++type Comment = Located Text++commentText :: Comment -> Text+commentText = value++newComment :: Int -> Text -> Comment+newComment i text = Located i text++endLine :: Comment -> Int+endLine (Located startLine c) = startLine + (Text.count "\n" c)++appendComment :: Comment -> Comment -> Maybe Comment+appendComment x y+  | startLine y - endLine x > 1 = Nothing+  | startLine y - endLine x == 1 = Just (newComment (startLine x) (commentText x <> "\n" <> commentText y))+  | otherwise = Just (newComment (startLine x) (commentText x <> commentText y))++getComment :: LocatedToken -> Maybe Comment+getComment (Located lineNum token) =+  case getComment' token of+    Nothing -> Nothing+    Just comment -> Just $ Located lineNum comment++getComment' :: Token -> Maybe Text+getComment' (AlertTok, value) = Just (toS value)+getComment' (CommentTok, value) = Just (toS value)+getComment' _ = Nothing+++-- | Wrappers around syntax highlighting code.++-- TODO: Move these to a separate module, maybe.++type Language = Text++languageForFile :: Text -> Maybe Language+languageForFile = fmap toS . head . languagesByFilename . toS++highlightCode :: Language -> Text -> [SourceLine]+highlightCode language code =+  let highlighted = highlightAs (toS language) (toS code)+  in case Text.toLower language of+       "haskell" -> map fixupHaskellComments highlighted+       _ -> highlighted++  where+    -- | For some reason, Kate highlights a Haskell comment with no text (i.e.+    -- "--") as a function. Fix this up.+    fixupHaskellComments = map (\t -> if t == (FunctionTok, "--") then (CommentTok, "--") else t)
+ src/Fixme/Diff.hs view
@@ -0,0 +1,106 @@+-- | Extract comments from diffs++module Fixme.Diff+  ( newCommentsFromDiff+  ) where++import Protolude++import qualified Data.Text as Text+import Text.Diff.Parse (parseDiff)+import Text.Diff.Parse.Types+  ( Annotation(..)+  , Content(..)+  , FileDelta(..)+  , FileStatus(..)+  , Hunk(..)+  , Line(..)+  )+import Fixme.Comment+  ( Comment+  , Language+  , languageForFile+  , parseComments+  , startLine+  , endLine+  )++-- TODO: Try to use lenses for this.++-- | Get the comments that were added or modified in a diff.+--+-- Silently ignores files if we can't figure out what their programming+-- language is.+newCommentsFromDiff :: ByteString -> Either Text [Comment]+newCommentsFromDiff =+  bimap toS (join . catMaybes . map getNewCommentsForFile) . parseDiff .toS+  where+    getNewCommentsForFile (FileDelta Deleted _ _ _) = Just []+    getNewCommentsForFile (FileDelta _ _ _ Binary) = Just []+    getNewCommentsForFile (FileDelta _ _ filename (Hunks hunks)) =+      case languageForFile filename of+        Nothing -> Nothing+        Just language -> Just $ concatMap (getNewCommentsForHunk language) hunks+++-- | Retrieve all the comments that were either added or modified in 'hunk'.+--+-- What we actually want to do is to find all of the TODOs that were added in+-- a branch by means of examining the diff between that branch and 'master',+-- so that we have a way of figuring out what's left to do while working on a+-- branch.+--+-- This means we can use a simplified approach to analyze the code:+--+--  * if a hunk has no insertions, then it can't be a pending todo+--  * if a hunk has no comments, then it can't be a pending todo+--  * if a comment appears in a hunk, but nothing was inserted, then it can't be a+--    pending todo+--  * if a comment appears in a hunk, and there is a line of insertion in that+--    comment, then it *might* be a pending todo+--+-- Here, "comment" means a contiguous sequence of comment tokens.+getNewCommentsForHunk :: Language -> Hunk -> [Comment]+getNewCommentsForHunk language hunk =+  let comments = parseComments language afterText+  in filterInsertions addedLineNumbers comments+  where+    -- | Reconstitute the "after" text of the diff hunk.+    -- That is, the text that you would get after applying the hunk as a patch.+    afterText :: Text+    afterText = Text.unlines $ map lineContent rightSide++    -- | Return only the comments that are touched by the given line numbers.+    --+    -- Assumes that:+    --   * comments are in order+    --   * comments are not overlapping+    --   * line numbers are sorted+    filterInsertions :: [Int] -> [Comment] -> [Comment]+    filterInsertions [] _ = []+    filterInsertions _ [] = []+    filterInsertions lineNums@(i:lineNums') comments@(c:comments') =+      case lineInComment i c of+        LT -> filterInsertions lineNums' comments+        EQ -> c:(filterInsertions lineNums comments')+        GT -> filterInsertions lineNums comments'++    -- | Is this line number before (LT), within (EQ), or after (GT) this+    -- comment?+    lineInComment :: Int -> Comment -> Ordering+    lineInComment lineNum comment+      | lineNum > endLine comment = GT+      | lineNum < startLine comment = LT+      | otherwise = EQ++    -- | Return the line numbers (of the right side of the diff) that are+    -- insertions.+    addedLineNumbers :: [Int]+    addedLineNumbers =+      [ i | (i, line) <- (zip [0..] rightSide)+          , lineAnnotation line == Added ]++    -- | Get the right-hand side of the diff. That is, only the insertions and+    -- context lines. We don't care about the "before", only the "after".+    rightSide :: [Line]+    rightSide = [ line | line <- hunkLines hunk, lineAnnotation line /= Removed ]
+ src/Fixme/Todo.hs view
@@ -0,0 +1,28 @@+-- | Get todos from comments++module Fixme.Todo+  ( getTodos+  , formatTodo+  ) where++import Protolude++import qualified Data.Text as Text+import Fixme.Comment (Comment, Located, commentText)+++type Todo = Located Text++getTodos :: Comment -> [Todo]+getTodos comment =+  if any (`Text.isInfixOf` commentText comment) defaultTags then+    [comment] else []++formatTodo :: Todo -> Text+formatTodo = ((<>) "\n") . commentText++defaultTags :: [Text]+defaultTags = [ "XXX"+              , "TODO"+              , "FIXME"+              ]
+ tests/Comment.hs view
@@ -0,0 +1,170 @@+module Comment (tests) where++import Protolude++import Data.Text (stripEnd, unlines)+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit++import Text.Highlighting.Kate (TokenType(..))++import Fixme.Comment+  ( parseComments'+  , parseComments+  , newComment+  , highlightCode+  )+++tests :: TestTree+tests =+  testGroup "Fixme.Comment"+  [ testGroup "Text.Highlighting.Kate"+    [ testCase "parse python" $ do+        let lexed = highlightCode "python" (toS pythonExample)+        let expected = [ [(CommentTok,"# first comment")]+                       , []+                       , [(CommentTok,"# second comment")]+                       , [(CommentTok,"# is multi-line")]+                       , []+                       , [(DecValTok,"1"),(NormalTok," "),(OperatorTok,"+"),(NormalTok," "),(DecValTok,"2"),(NormalTok,"  "),(CommentTok,"# trailing comment")]+                       ]+        expected @=? lexed+    , testCase "parse haskell" $ do+        let lexed = highlightCode "haskell" (toS haskellExample)+        let expected = [ [(CommentTok,"-- first comment")]+                       , []+                       , [(CommentTok,"-- second comment")]+                       , [(CommentTok,"-- is multi-line")]+                       , []+                       , [(CommentTok,"{- comment block start")]+                       , [(CommentTok,"continues")]+                       , [(CommentTok,"ends -}")]+                       , []+                       , [ (NormalTok,"f "),(FunctionTok,"="),(NormalTok," "),(CommentTok,"{- foo -}"),(NormalTok," ")+                         , (StringTok,"\"bar\""),(NormalTok," "),(CommentTok,"{- baz -}"),(NormalTok," ")+                         , (CommentTok,"-- qux")+                         ]+                       ]+        expected @=? lexed+    , testCase "parse indented haskell" $ do+        let lexed = highlightCode "haskell" (toS indentedHaskellExample)+        let expected = [ [(CommentTok,"-- first comment")]+                       , []+                       , [(NormalTok," "),(NormalTok," "),(CommentTok,"-- second comment")]+                       , [(NormalTok," "),(NormalTok," "),(CommentTok,"-- is multi-line")]+                       , []+                       , [(CommentTok,"{- comment block start")]+                       , [(CommentTok,"   continues")]+                       , [(CommentTok,"   ends -}")]+                       ]+        expected @=? lexed+    , testCase "parse comment with newline" $ do+        let example = unlines $ [ "-- first line"+                                , "--"+                                , "-- second line"+                                ]+        let expected = [ [(CommentTok,"-- first line")]+                       , [(CommentTok,"--")]+                       , [(CommentTok,"-- second line")]+                       ]+        expected @=? highlightCode "haskell" (toS example)+    , testCase "parse comment with newline (indented)" $ do+        let example = unlines $ [ "  -- first line"+                                , "  --"+                                , "  -- second line"+                                ]+        let expected = [ [(NormalTok," "),(NormalTok," "),(CommentTok,"-- first line")]+                       , [(NormalTok," "),(NormalTok," "),(CommentTok,"--")]+                       , [(NormalTok," "),(NormalTok," "),(CommentTok,"-- second line")]+                       ]+        expected @=? highlightCode "haskell" (toS example)+    , testCase "parse comment with newline (python)" $ do+        let example = unlines $ [ "# first line"+                                , "#"+                                , "# second line"+                                ]+        let expected = [ [(CommentTok,"# first line")]+                       , [(CommentTok,"#")]+                       , [(CommentTok,"# second line")]+                       ]+        expected @=? highlightCode "python" (toS example)+    ]+  , testGroup "Parsing comments from tokens"+    [ testCase "Parse Python example" $ do+        let input = [ [(CommentTok,"# first comment")]+                    , []+                    , [(CommentTok,"# second comment")]+                    , [(CommentTok,"# is multi-line")]+                    , []+                    , [(DecValTok,"1"),(NormalTok," "),(OperatorTok,"+"),(NormalTok," "),(DecValTok,"2"),(NormalTok,"  "),(CommentTok,"# trailing comment")]+                    ]+        let expected = [ newComment 0 "# first comment"+                       , newComment 2 "# second comment\n# is multi-line"+                       , newComment 5 "# trailing comment"+                       ]+        expected @=? parseComments' input+    , testCase "Parse indented example" $ do+        let input = [ [(NormalTok," "),(NormalTok," "),(CommentTok,"-- second comment")]+                    , [(NormalTok," "),(NormalTok," "),(CommentTok,"-- is multi-line")]+                    ]+        let expected = [ newComment 0 "-- second comment\n-- is multi-line" ]+        expected @=? parseComments' input+    , testCase "Commented blank line continues comment" $ do+        let input = [ [(CommentTok,"# comment")]+                    , [(CommentTok,"#")]+                    , [(CommentTok,"# is multi-line")]+                    ]+        let expected = [ newComment 0 "# comment\n#\n# is multi-line" ]+        expected @=? parseComments' input+    ]+  , testGroup "Parsing comments from source"+    [ testCase "Multi-line Haskell comment" $ do+        let example = unlines $+              [ "-- | First line."+              , "--"+              , "-- Explanatory"+              , "-- paragraph."+              , "--"+              , "-- Conclusion"+              ]+        let expected = [ newComment 0 (stripEnd example) ]+        expected @=? parseComments "haskell" example+    ]+  ]++pythonExample :: Text+pythonExample = unlines $+  [ "# first comment"+  , ""+  , "# second comment"+  , "# is multi-line"+  , ""+  , "1 + 2  # trailing comment"+  ]++haskellExample :: Text+haskellExample = unlines $+  [ "-- first comment"+  , ""+  , "-- second comment"+  , "-- is multi-line"+  , ""+  , "{- comment block start"+  , "continues"+  , "ends -}"+  , ""+  , "f = {- foo -} \"bar\" {- baz -} -- qux"+  ]++indentedHaskellExample :: Text+indentedHaskellExample = unlines $+  [ "-- first comment"+  , ""+  , "  -- second comment"+  , "  -- is multi-line"+  , ""+  , "{- comment block start"+  , "   continues"+  , "   ends -}"+  ]
+ tests/Diff.hs view
@@ -0,0 +1,56 @@+module Diff (tests) where++import Protolude++import qualified Data.ByteString.Char8+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit++import Fixme.Comment (newComment)+import Fixme.Diff (newCommentsFromDiff)+++tests :: TestTree+tests =+  testGroup "Fixme.Diff"+  [ testCase "New file" $ do+      let input = Data.ByteString.Char8.unlines $+            [ "diff --git a/src/Fixme/Diff.hs b/src/Fixme/Diff.hs"+            , "new file mode 100644"+            , "index 0000000..c926970"+            , "--- /dev/null"+            , "+++ b/src/Fixme/Diff.hs"+            , "@@ -0,0 +1,8 @@"+            , "+-- | Extract comments from diffs"+            , "+"+            , "+module Fixme.Diff"+            , "+  ( parseComments"+            , "+  , newCommentsFromDiff"+            , "+  , newComment"+            , "+  , comment"+            , "+  ) where"+            ]+      let expected = [ newComment 0 "-- | Extract comments from diffs"]+      Right expected @=? newCommentsFromDiff input+  , testCase "Multi-line comment" $ do+      let input = Data.ByteString.Char8.unlines $+            [ "diff --git a/src/Fixme/Diff.hs b/src/Fixme/Diff.hs"+            , "new file mode 100644"+            , "index 0000000..c926970"+            , "--- /dev/null"+            , "+++ b/src/Fixme/Diff.hs"+            , "@@ -0,0 +1,10 @@"+            , "+-- | Extract comments from diffs"+            , "+--"+            , "+-- Yep."+            , "+"+            , "+module Fixme.Diff"+            , "+  ( parseComments"+            , "+  , newCommentsFromDiff"+            , "+  , newComment"+            , "+  , comment"+            , "+  ) where"+            ]+      let expected = [ newComment 0 "-- | Extract comments from diffs\n--\n-- Yep."]+      Right expected @=? newCommentsFromDiff input+  ]
+ tests/Main.hs view
@@ -0,0 +1,19 @@+module Main (main) where++import Protolude++import Test.Tasty (defaultMain, TestTree, testGroup)++import qualified Comment+import qualified Diff+++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests =+  testGroup "Fixme"+  [ Diff.tests+  , Comment.tests+  ]