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 +163/−0
- Setup.hs +2/−0
- all-todos/Main.hs +59/−0
- difftodo.cabal +72/−0
- src/Fixme.hs +17/−0
- src/Fixme/Comment.hs +111/−0
- src/Fixme/Diff.hs +106/−0
- src/Fixme/Todo.hs +28/−0
- tests/Comment.hs +170/−0
- tests/Diff.hs +56/−0
- tests/Main.hs +19/−0
+ 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+ ]