weeder 0.1.2 → 0.1.3
raw patch · 6 files changed
+62/−11 lines, 6 files
Files
- CHANGES.txt +5/−0
- README.md +6/−2
- src/Check.hs +18/−2
- src/Hi.hs +17/−3
- src/Warning.hs +15/−3
- weeder.cabal +1/−1
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