diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ed255ce..055b99c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,8 @@ build-job: # This job runs in the build stage, which runs first. script: - make release artifacts: - - build/dist/*.tar.gz + paths: + - build/dist/*.tar.gz unit-test-job: # This job runs in the test stage. stage: test # It only starts when the job in the build stage completes successfully. @@ -38,7 +39,7 @@ unit-test-job: # This job runs in the test stage. - linux script: - echo "Running unit tests... This will take about 60 seconds." - - sleep 60 + - sleep 1 - echo "Code coverage is 90%" lint-test-job: # This job also runs in the test stage. diff --git a/Makefile b/Makefile index 970b7fd..b656f16 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,32 @@ -.PHONY: default all install watch clean build release distclean lint +.PHONY: default all install watch clean build release distclean lint doc default: all all: build -build: builddir build/bin/annotator - GHCFLAGS = -iapp -isrc -Wall -O2 -outputdir build/.obj BUILDDIR = build DESTDIR = ~/.local BINDIR = ${DESTDIR}/bin DOCDIR = ${DESTDIR}/share/doc/annotator +build: builddir | $(BUILDDIR)/bin/annotator doc + builddir: - mkdir -p $(BUILDDIR)/bin $(BUILDDIR)/.obj $(BUILDDIR)/ci $(BUILDDIR)/dist + mkdir -p $(BUILDDIR)/{bin,.obj,ci,dist,doc} + +doc: builddir | $(BUILDDIR)/doc/README.md $(BUILDDIR)/bin/annotator: app/Main.hs src/*.hs ghc --make $(GHCFLAGS) $< -o $@ +$(BUILDDIR)/doc/README.md: README.org + pandoc -t gfm $< -o $@ + watch: @git ls-files src app Makefile | entr make -s build install -lint: - @-hlint -g --cc > $(BUILDDIR)/ci/hlint.report.json +lint: builddir + hlint lint --git --cc --no-exit-code | tr -d '\0' | jq -s . > $(BUILDDIR)/ci/hlint.report.json clean: @rm -rf $(BUILDDIR) @@ -31,9 +36,10 @@ clean: distclean: clean @rm -rf dist -install: $(BUILDDIR)/bin/annotator - @install -m 0755 -D -t ${BINDIR} $< - @install -m 0644 -D -t ${DOCDIR} README.md +install: build + @install -m 0755 -D -t ${BINDIR} $(BUILDDIR)/bin/annotator + @install -m 0644 -D -t ${DOCDIR} README.org + @install -m 0644 -D -t ${DOCDIR} $(BUILDDIR)/doc/README.md release: script/make-release @$< diff --git a/README.md b/README.md deleted file mode 100644 index bae8d08..0000000 --- a/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Annotator - an interactive TM2/TM3 annotation tool - -This tool lets you interactively annotate your code given a =defects.err= file. - -## Installation - -Get the most recent release, unpack it and add the binary to your PATH. - -## How to use it - -After a /Coverity™/ run, you end up with a =defects.err= and would like to -annotate your code accordingly. In order to do so, just execute the =annotator=: - -``` -annotator -``` - -This will by default use the =defects.err= file in the current directory and -scan it for violations. It will then ask you what to do with each violation - by -default only /Newest/ violations will be handled, but this can be overridden -with a command-line switch. - -After all violations have been treated, you'll end up with a bunch of =*.fix= -files next to each source file - those are the annotated source files - you may -run diff on them to check if they look fine or just move them over your original -source file. - -### Possible annotations - -The annotator is able to generate three kinds of annotations: - - - *Intentional* - by pressing `i`, this will annotate with `coverity[rule] ` - - *False-Positive* - by pressing `f`, this will annotate with `coverity[rule : FALSE] ` - - *Todo* - by pressing `t`, this will annotate with a FIXME marker - -## Advanced usage scenarios - -The current annotator supports the following command-line arguments: - -``` -$ annotator --help -Usage: annotator [OPTIONS] files... - -A tool to semi-automatically add Coverity source-code annotations based on found defects. - - - -v --verbose be more verbose, pass multiple times to increase verbosity - -i --inplace replace source-file after inserting annotations - -V --version show full version information - --short-version show just the version number - -h --help show usage information - -a --all handle all defects not just Newest - -C[NUM] --context[=NUM] specify how much context should be shown around a violation - -t[STRING] --todo-marker[=STRING] override the default TODO marker with a custom string - -A FILE --annotations=FILE load automatic annotation rules -``` - -### In-place annotations - -The `annotator` allows to annotate in-place, i.e. it will automatically rename -the `.fix` file after you are done with all violations within that file. This -can be achieved by passing `-i` or `--inplace`. - -### Process all violations - -By default the annotator will only handle *Newest* violations and not those that -are already contained within the Coverity database for some reason. However, -it's still possible to process all found violations by passing `-a` or `--all` -on the command-line. - -### Insert annotations automatically - -In case you have violations that always result in the same annotation over and -over again, you can supply one or more files that contain automatic decisions. - -Each line may be one of the following: - -``` -Intentional (Rule "autosar_cpp14_a18_9_1") "reason why it's intentional" -FalsePositive (Rule "autosar_cpp14_a18_9_1") "reason why it's a false-positive" -ToDo (Rule "autosar_cpp14_a18_9_1") -``` - -Lines may be disabled by prefixing them with `#` or `--`. diff --git a/README.org b/README.org index 3e55f92..591bada 100644 --- a/README.org +++ b/README.org @@ -1,14 +1,54 @@ -#+title: Readme +#+title: Annotator - an interactive Coverity annotation tool + +* Abstract + +This tool lets you interactively annotate your code given a =defects.err= file. The file should contain file and line information along with the Coverity rule that was violated. An example may look like this: + +#+begin_example csv +main.cpp:162:INFO: Newest, autosar_cpp14_a20_8_6_violation: Object "std::unique_ptr >(read_configuration(uri))" is not constructed using "std::make_shared". +#+end_example * Installation +Get the most recent release, unpack it and add the binary to your PATH. + * Usage -#+begin_src sh :results output +After a /Coverity™/ run, you end up with a =defects.err= and would like to +annotate your code accordingly. In order to do so, just execute the =annotator=: + +#+begin_src sh +annotator +#+end_src + +This will by default use the =defects.err= file in the current directory and +scan it for violations. It will then ask you what to do with each violation - by +default only /Newest/ violations will be handled, but this can be overridden +with a command-line switch. + +After all violations have been treated, you'll end up with a bunch of =*.fix= +files next to each source file - those are the annotated source files - you may +run diff on them to check if they look fine or just move them over your original +source file. + +** Possible annotations + +The annotator is able to generate three kinds of annotations: + +- Intentional :: by pressing =i=, this will annotate with =coverity[rule] = +- FalsePositive :: by pressing =f=, this will annotate with =coverity[rule : FALSE] = +- Todo :: by pressing =t=, this will annotate with a TODO marker + +* Advanced usage scenarios + +The current annotator supports the following command-line arguments: + +#+name: annotator-help +#+begin_src sh :results output :exports both annotator --help #+end_src -#+RESULTS: +#+RESULTS: annotator-help #+begin_example Usage: annotator [OPTIONS] files... @@ -32,3 +72,25 @@ A tool to semi-automatically add Coverity source-code annotations based on found # a comment -- another comment #+end_example + +** In-place annotations + +The =annotator= allows to annotate in-place, i.e. it will automatically rename the =.fix= file after you are done with all violations within that file. This can be achieved by passing =-i= or =--inplace=. + +** Process all violations + +By default the annotator will only handle *Newest* violations and not those that are already contained within the Coverity database for some reason. However, it's still possible to process all found violations by passing =-a= or =--all= on the command-line. + +** Insert annotations automatically + +In case you have violations that always result in the same annotation over and over again, you can supply one or more files that contain automatic decisions. + +Each line may be one of the following: + +#+begin_example haskell +Intentional (Rule "autosar_cpp14_a18_9_1") "reason why it's intentional" +FalsePositive (Rule "autosar_cpp14_a18_9_1") "reason why it's a false-positive" +ToDo (Rule "autosar_cpp14_a18_9_1") +#+end_example + +Lines may be disabled by prefixing them with =#= or =--=. diff --git a/example/include/process/definition_receiver.hpp b/example/include/process/definition_receiver.hpp index a4ee945..0dc521c 100644 --- a/example/include/process/definition_receiver.hpp +++ b/example/include/process/definition_receiver.hpp @@ -4,7 +4,7 @@ #include #include -#include "process_definition.hpp" // TODO generate to +#include "process_definition.hpp" namespace process::controller { class DefinitionListener; diff --git a/example/include/process/predicate.hpp b/example/include/process/predicate.hpp index eefc8df..ae96cee 100644 --- a/example/include/process/predicate.hpp +++ b/example/include/process/predicate.hpp @@ -1,7 +1,7 @@ #ifndef PROCESS_CONTROLLER_PREDICATE_HPP #define PROCESS_CONTROLLER_PREDICATE_HPP -#include "process_definition.hpp" // TODO: generate to /idl/process/process_definition.hpp +#include "process_definition.hpp" #include #include @@ -62,8 +62,6 @@ namespace predicate { /** * returns a function that checks whether the node of a ProcessDefinition matches a given pattern - * - * TODO implement more sophisticated glob matching, we currently support '*' and exact match */ [[nodiscard]] inline std::function node_matches(const std::string& pattern) noexcept { diff --git a/src/Annotator.hs b/src/Annotator.hs index 90191c0..eacc283 100644 --- a/src/Annotator.hs +++ b/src/Annotator.hs @@ -1,7 +1,6 @@ -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE StrictData #-} module Annotator (defaultMain) where @@ -17,7 +16,7 @@ import Control.Monad.IO.Class import Control.Monad.Trans.Reader import Control.Exception (evaluate) import Data.Function (on) -import Data.List (delete, intercalate, foldl', sortBy, nub) +import Data.List (delete, foldl', sortOn, nub) import Data.Char (isSpace) import Data.Either (rights) import Data.Ord @@ -31,7 +30,7 @@ import Annotator.Rule import Annotator.Annotation version :: Vsn.Version -version = Vsn.makeVersion [0,0,5,1] +version = Vsn.makeVersion [0,0,5,2] type App a = ReaderT Options IO a @@ -104,7 +103,7 @@ options = (ReqArg (\f opts -> opts { todoMarker = f }) "STRING") "override the default TODO marker with a custom string" , Option ['A'] ["annotations"] - (ReqArg (\f opts -> opts { annotationFiles = (nub $ annotationFiles opts ++ [f]) }) "FILE") + (ReqArg (\f opts -> opts { annotationFiles = f : annotationFiles opts }) "FILE") (unlines [ "load automatic annotation rules" , " some examples:" , " " <> show (Intentional (Rule "rule_1") "some reason") @@ -124,7 +123,9 @@ readMaybe s = case reads s of parseOptions :: [String] -> IO (Options, [String]) parseOptions argv = case getOpt Permute options argv of - (o, n, []) -> pure (foldl' (flip id) defaultOptions o, n) + (o, n, []) -> do + let o' = foldl' (flip id) defaultOptions o + pure (o'{annotationFiles = reverse $ nub $ annotationFiles o'}, n) (_, _, errs) -> ioError (userError (concat errs ++ usageInfo header options)) where header = "Usage: annotator [OPTIONS] files..." @@ -145,18 +146,18 @@ defaultMain = do Just FullVersion -> do putStr $ unlines [ - "Annotator v" <> (Vsn.showVersion version) + "Annotator v" <> Vsn.showVersion version , "Copyright (c) 2022 Alexander Kobjolke " ] - exitWith ExitSuccess + exitSuccess Just ShortVersion -> do putStrLn $ Vsn.showVersion version - exitWith ExitSuccess + exitSuccess Nothing -> pure () when (showHelp opts) $ do putStr $ usageInfo header options - exitWith ExitSuccess + exitSuccess automaticAnnotations <- rights . concat <$> (filterM fileExist (annotationFiles opts) >>= mapM fromFile) @@ -165,9 +166,8 @@ defaultMain = do runReaderT (genericMain fn) opts' where - header = unlines $ - [ - "Usage: annotator [OPTIONS] files..." + header = unlines + [ "Usage: annotator [OPTIONS] files..." , "" , "A tool to semi-automatically add Coverity source-code annotations based on found defects." , "" @@ -177,11 +177,11 @@ data Age = Old | New | Newest deriving (Show, Eq) data Violation = Violation { - filename :: !FilePath, - line :: !Int, - age :: !Age, - rule :: !Rule, - description :: !String + filename :: FilePath, + line :: Int, + age :: Age, + rule :: Rule, + description :: String } deriving (Show, Eq) @@ -190,7 +190,7 @@ split _ _ [] = [] split n p xs | n > 0 = case break p xs of (match, []) -> [match] - (match, (_ : rest)) -> match : split (n-1) p rest + (match, _ : rest) -> match : split (n-1) p rest | otherwise = [xs] parseViolations :: String -> [Violation] @@ -208,7 +208,7 @@ parseViolation s = case split 4 (== ':') s of where violation = Violation file (read line) a (Rule (removeSuffix "_violation" r)) (dropWhile isSpace $ concat desc) (_:age':r:_) = split 2 (== ' ') rule' - a = case (delete ',' age') of + a = case delete ',' age' of "Newest" -> Newest "New" -> New _ -> Old @@ -230,7 +230,7 @@ genericMain file = do verbose Chatty $ "all violations: " <> show sortedViolations forM_ groupedViolations handleViolations - Nothing -> do + Nothing -> liftIO $ hPutStrLn stderr "Defects file is empty" where @@ -249,10 +249,10 @@ handleViolations violations = do let fname = filename $ NE.head violations fname' = fname <> ".fix" todo = todoMarker opts - header' = "Processing " <> (show $ NE.length violations) <> " violation(s) in file " <> fname + header' = "Processing " <> show (NE.length violations) <> " violation(s) in file " <> fname header = unlines [ header' - , take (length header') $ repeat '=' + , replicate (length header') '=' , "" ] @@ -268,15 +268,15 @@ handleViolations violations = do let numberedContent = zip [1..] . lines $ content - annotations <- (catMaybes . NE.toList) <$> mapM (handleViolation content) violations + annotations <- catMaybes . NE.toList <$> mapM (handleViolation content) violations - let annotatedLines = sortBy (comparing fst) $ fmap (annotationToLine todo) annotations + let annotatedLines = sortOn fst $ fmap (annotationToLine todo) annotations newContent = unlines . map snd $ mergeLines annotatedLines numberedContent liftIO $ writeFile fname' newContent liftIO $ when (inplace opts) $ rename fname' fname - else do + else verbose Low $ "skipping non-existent file " <> fname where annotationToLine :: String -> AnnotatedViolation -> (Int, String) @@ -296,24 +296,17 @@ data UserChoice = Abort -- | let the user decide what to do with a violation getUserChoice :: Violation -> App UserChoice -getUserChoice Violation{..} = do - liftIO $ queryUser +getUserChoice Violation{..} = liftIO queryUser where queryUser = do - putStr $ "> What shall we do [s/t/i/f/q/?]: " + putStr "> What shall we do [s/t/i/f/q/?]: " hFlush stdout c <- getChar putStrLn "" case c of - 's' -> do - pure Skip - 't' -> do - pure $ Annotate (ToDo rule) - 'i' -> do - excuse <- getExcuse - pure $ Annotate (Intentional rule excuse) - 'f' -> do - excuse <- getExcuse - pure $ Annotate (FalsePositive rule excuse) + 's' -> pure Skip + 't' -> pure $ Annotate (ToDo rule) + 'i' -> Annotate . Intentional rule <$> getExcuse + 'f' -> Annotate . FalsePositive rule <$> getExcuse 'q' -> pure Abort '?' -> do putStrLn $ unlines [ "t - add TODO marker to fix this issue" @@ -328,7 +321,7 @@ getUserChoice Violation{..} = do _ -> queryUser getExcuse = do - putStr $ "What's your excuse? " + putStr "What's your excuse? " hFlush stdout mode <- hGetBuffering stdin hSetBuffering stdin LineBuffering @@ -349,7 +342,7 @@ type NumberedLine = (Int, String) mergeLines :: [NumberedLine] -> [NumberedLine] -> [NumberedLine] mergeLines [] r = r mergeLines l [] = l -mergeLines !lhs@(left@(nl,_):ls) !rhs@(right@(nr,_):rs) +mergeLines lhs@(left@(nl,_):ls) rhs@(right@(nr,_):rs) | nl <= nr = left : mergeLines ls rhs | otherwise = right : mergeLines lhs rs @@ -379,21 +372,21 @@ handleViolation content v@Violation{..} = do -- print some context liftIO $ forM_ context (\(n, code) -> do let marker = ">>>>" - when (n == line) $ putStrLn (intercalate " " [ marker - , show age - , "violation of rule" - , show rule - , "in line" - , show line <> ":" - , description - ]) - putStrLn (code)) + when (n == line) $ putStrLn (unwords [ marker + , show age + , "violation of rule" + , show rule + , "in line" + , show line <> ":" + , description + ]) + putStrLn code) if batchMode opts - then pure $ Nothing + then pure Nothing else do choice <- getUserChoice v case choice of - Abort -> liftIO $ exitSuccess + Abort -> liftIO exitSuccess Annotate annotation -> pure $ Just (AnnotatedViolation v annotation indent) Skip -> pure Nothing Help -> handleViolation content v diff --git a/src/Annotator/Annotation.hs b/src/Annotator/Annotation.hs index 5f9e6a8..ae092f5 100644 --- a/src/Annotator/Annotation.hs +++ b/src/Annotator/Annotation.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE LambdaCase #-} module Annotator.Annotation (Annotation(..), fromFile) where import Data.Char (isSpace) diff --git a/src/Annotator/Util.hs b/src/Annotator/Util.hs index 4fb234f..2ce4b2c 100644 --- a/src/Annotator/Util.hs +++ b/src/Annotator/Util.hs @@ -1,4 +1,4 @@ module Annotator.Util where anyp :: [a -> Bool] -> a -> Bool -anyp preds x = or (map ($ x) preds) +anyp preds x = any ($ x) preds