packages feed

weeder 0.1.2 → 0.1.3

raw patch · 6 files changed

+62/−11 lines, 6 files

Files

CHANGES.txt view
@@ -1,5 +1,10 @@ Changelog for Weeder +0.1.3+    #5, document how to install weeder+    #8, detect unused imports, even import Foo()+    #7, don't say modules with only instances are always redundant+    #6, don't give partial pattern matches when reading .weeder.yaml 0.1.2     #3, deal with space-separated hs-source-dirs 0.1.1
README.md view
@@ -8,8 +8,12 @@  ## Running Weeder -To use `weeder` your code must have been built with [`stack`](https://www.haskellstack.org), as it piggy-backs off files that `stack` generates. If you don't normally build with `stack` a simple `stack init && weeder . --build` is likely to be sufficient to get you started.+Weeder piggy-backs off files generated by [`stack`](https://www.haskellstack.org), so first obtain stack, then: +* Install `weeder` by running `stack install weeder --resolver=nightly`.+* Ensure your project has a `stack.yaml` file. If you don't normally build with `stack` then run `stack init` to generate one.+* Run `weeder . --build`, which builds your project with `stack` and checks it for weeds.+ ## What does Weeder detect?  Weeder detects a bunch of weeds, including:@@ -28,7 +32,7 @@  If you want your package to be detected as "weed free", but it has some weeds you know about but don't consider important, you can add a `.weeder.yaml` file adjacent to the `stack.yaml` with a list of exclusions. To generate an initial list of exclusions run `weeder . --yaml > .weeder.yaml`. -You may then with to generalise/simplify the `.weeder.yaml` by removing anything above or below the interesting part. As an example of the [`.weeder.yaml` file from `ghcid`](https://github.com/ndmitchell/ghcid/blob/master/.weeder.yaml):+You may wish to generalise/simplify the `.weeder.yaml` by removing anything above or below the interesting part. As an example of the [`.weeder.yaml` file from `ghcid`](https://github.com/ndmitchell/ghcid/blob/master/.weeder.yaml):  ```yaml - message: Module reused between components
src/Check.hs view
@@ -5,6 +5,7 @@ import Hi import Cabal import Util+import Data.Maybe import Data.List.Extra import Data.Tuple.Extra import qualified Data.HashSet as Set@@ -24,7 +25,8 @@     warnRedundantPackageDependency s ++     warnIncorrectOtherModules s ++     warnUnusedExport s ++-    warnNotCompiled pkg sections2+    warnNotCompiled pkg sections2 +++    warnUnusedImport s     where         s = S{..}         sections = map (second $ \(a,b,c) -> let aa = nubOrd a in (aa,nubOrd b \\ aa)) sections2@@ -56,7 +58,7 @@     [ [Warning pkg [cabalSectionType] "Missing other-modules entry" Nothing (Just m) Nothing | m <- Set.toList missing] ++       [Warning pkg [cabalSectionType] "Excessive other-modules entry" Nothing (Just m) Nothing | m <- Set.toList excessive]     | (CabalSection{..}, (external, internal)) <- sections-    , let imports = Map.fromList [(hiModuleName, Set.map identModule hiImportIdent) | Hi{..} <- map hi $ external ++ internal]+    , let imports = Map.fromList [(hiModuleName, hiImportModule) | Hi{..} <- map hi $ external ++ internal]     , let missing =  Set.filter (not . isPathsModule) $                      Set.unions (Map.elems imports) `Set.difference`                      Set.fromList (Map.keys imports)@@ -64,6 +66,20 @@                       reachable (\k -> maybe [] Set.toList $ Map.lookup k imports) (map (hiModuleName . hi) external)     ] ++-- Primarily looking for import Foo() where Foo is not an orphan+warnUnusedImport :: S -> [Warning]+warnUnusedImport S{..} =+    [ Warning pkg [cabalSectionType] "Unused import" Nothing (Just $ hiModuleName mod) (Just $ hiModuleName imp)+    | (CabalSection{..}, (external, internal)) <- sections+    , let mods = Map.fromList $ map ((hiModuleName &&& id) . hi) $ external ++ internal+    , mod <- Map.elems mods+    , imp <- mapMaybe (`Map.lookup` mods) $ Set.toList $+        hiImportModule mod `Set.difference`+        (Set.map identModule (hiImportIdent mod) `Set.union` hiImportOrphan mod)+    , Set.null $ hiImportIdent mod `Set.intersection` hiExportIdent imp -- reexporting for someone else+    , Set.null $ Set.map snd (hiImportPackageModule mod) `Set.intersection` Set.map identModule (hiExportIdent imp) -- reexporting for another package +    ]  warnUnusedExport :: S -> [Warning] warnUnusedExport S{..} =
src/Hi.hs view
@@ -34,6 +34,13 @@         -- ^ Identifiers exported by this module     ,hiImportIdent :: Set.HashSet Ident         -- ^ Identifiers used by this module+    ,hiImportModule :: Set.HashSet ModuleName+        -- ^ Modules imported and used by this module+        --   Normally equivalent to @Set.map identModule hiImportIdent@, unless a module supplies only instances+    ,hiImportOrphan :: Set.HashSet ModuleName+        -- ^ Orphans that are in scope in this module+    ,hiImportPackageModule :: Set.HashSet (PackageName, ModuleName)+        -- ^ Modules imported from other packages     ,hiSignatures :: Map.HashMap IdentName (Set.HashSet Ident)         -- ^ Type signatures of functions defined in this module and the types they refer to     ,hiFieldName :: Set.HashSet Ident@@ -42,12 +49,15 @@ instance Hashable Hi  instance Monoid Hi where-    mempty = Hi mempty mempty mempty mempty mempty mempty+    mempty = Hi mempty mempty mempty mempty mempty mempty mempty mempty mempty     mappend x y = Hi         {hiModuleName = f (?:) hiModuleName         ,hiImportPackage = f (<>) hiImportPackage         ,hiExportIdent = f (<>) hiExportIdent         ,hiImportIdent = f (<>) hiImportIdent+        ,hiImportModule = f (<>) hiImportModule+        ,hiImportPackageModule = f (<>) hiImportPackageModule+        ,hiImportOrphan = f (<>) hiImportOrphan         ,hiSignatures = f (Map.unionWith (<>)) hiSignatures         ,hiFieldName = f (<>) hiFieldName         }@@ -79,10 +89,14 @@         f (x,xs)             | Just x <- stripPrefix "interface " x = mempty{hiModuleName = parseInterface x}             | Just x <- stripPrefix "exports:" x = mconcat $ map parseExports xs+            | Just x <- stripPrefix "orphans:" x = mempty{hiImportOrphan = Set.fromList $ map parseInterface $ concatMap words $ x:xs}             | Just x <- stripPrefix "package dependencies:" x = mempty{hiImportPackage = Set.fromList $ map parsePackDep $ concatMap words $ x:xs}             | Just x <- stripPrefix "import " x = case xs of-                [] -> mempty -- these are imports of modules from another package, we don't know what is actually used-                xs -> mempty{hiImportIdent = Set.fromList $ map (Ident (words x !! 1) . fst . word1) $ dropWhile ("exports:" `isPrefixOf`) xs}+                [] | (pkg, mod) <- breakOn ":" $ words x !! 1 -> mempty+                    {hiImportPackageModule = Set.singleton (takeWhile (/= '@') pkg, drop 1 mod)}+                xs -> let m = words x !! 1 in mempty+                    {hiImportModule = Set.singleton m+                    ,hiImportIdent = Set.fromList $ map (Ident m . fst . word1) $ dropWhile ("exports:" `isPrefixOf`) xs}             | length x == 32, all isHexDigit x,                 (y,ys):_ <- parseHanging xs,                 fun:"::":typ <- concatMap (wordsBy (`elem` ",()[]{} ")) $ y:ys,
src/Warning.hs view
@@ -64,6 +64,7 @@ -- (section, name, children) data Val = Val String String [Val]          | End String [String]+           deriving Show  valToValue :: [Val] -> Value valToValue = Array . V.fromList . map f@@ -77,12 +78,17 @@ valueToVal :: Value -> [Val] valueToVal = f     where+        badYaml want x = error $ "Failed to understand Yaml fragment, expected " ++ want ++ ", got:\n" ++ BS.unpack (Yaml.encode x)+         f (Array xs) = concatMap f $ V.toList xs         f (Object mp) | [(k,v)] <- Map.toList mp = return $ case v of             v | Just (n, rest) <- findName v -> Val (T.unpack k) (T.unpack n) $ f rest-            Array xs -> End (T.unpack k) $ map (T.unpack . fromString) $ V.toList xs+            Array xs | Just xs <- mapM fromString $ V.toList xs -> End (T.unpack k) xs             String x -> End (T.unpack k) [T.unpack x]-        fromString (String x) = x+            _ -> badYaml "either a dict with 'name' or a list/single string" $ Object mp+        f x = badYaml "either a dict or an array" x+        fromString (String x) = Just $ T.unpack x+        fromString x = Nothing          findName (Array xs)             | ([name], rest) <- partition (isJust . fromName) $ V.toList xs@@ -113,7 +119,9 @@ readWarningsFile :: FilePath -> IO [Warning] readWarningsFile file = do     x <- either throwIO return =<< Yaml.decodeFileEither file-    return $ map warningUnpath $ concatMap (f warningLabels) $ valueToVal x+    let res = map warningUnpath $ concatMap (f warningLabels) $ valueToVal x+    mapM_ evaluate res -- ensure exceptions happen immediately+    return res     where         f :: [String] -> Val -> [[String]]         f names (End sect ns) = concatMap (\n -> f names $ Val sect n []) ns@@ -121,6 +129,10 @@             | sect == name = if null xs                 then [n : replicate (length names) ""]                 else map (n:) $ concatMap (f names) xs+            | sect `notElem` names = error $+                "Warnings file " ++ file ++ ", invalid section name:\n" +++                "Wanted one of: " ++ show (name:names) ++ "\n" +++                "Got: " ++ show sect             | otherwise = map ("":) $ f names val  
weeder.cabal view
@@ -1,7 +1,7 @@ cabal-version:      >= 1.18 build-type:         Simple name:               weeder-version:            0.1.2+version:            0.1.3 license:            BSD3 license-file:       LICENSE category:           Development