hlint-2.0.11/0000755000000000000000000000000013210071537011076 5ustar0000000000000000hlint-2.0.11/Setup.hs0000644000000000000000000000005613210071537012533 0ustar0000000000000000import Distribution.Simple main = defaultMain hlint-2.0.11/README.md0000644000000000000000000005053413210071537012364 0ustar0000000000000000# HLint [![Hackage version](https://img.shields.io/hackage/v/hlint.svg?label=Hackage)](https://hackage.haskell.org/package/hlint) [![Stackage version](https://www.stackage.org/package/hlint/badge/lts?label=Stackage)](https://www.stackage.org/package/hlint) [![Linux Build Status](https://img.shields.io/travis/ndmitchell/hlint.svg?label=Linux%20build)](https://travis-ci.org/ndmitchell/hlint) [![Windows Build Status](https://img.shields.io/appveyor/ci/ndmitchell/hlint.svg?label=Windows%20build)](https://ci.appveyor.com/project/ndmitchell/hlint) HLint is a tool for suggesting possible improvements to Haskell code. These suggestions include ideas such as using alternative functions, simplifying code and spotting redundancies. You can try HLint online at [lpaste.net](http://lpaste.net/) - suggestions are shown at the bottom. This document is structured as follows: * [Installing and running HLint](#installing-and-running-hlint) * [FAQ](#faq) * [Customizing the hints](#customizing-the-hints) * [Hacking HLint](#hacking-hlint) ### Acknowledgements This program has only been made possible by the presence of the [haskell-src-exts](https://github.com/haskell-suite/haskell-src-exts) package, and many improvements have been made by [Niklas Broberg](http://www.nbroberg.se) in response to feature requests. Additionally, many people have provided help and patches, including Lennart Augustsson, Malcolm Wallace, Henk-Jan van Tuyl, Gwern Branwen, Alex Ott, Andy Stewart, Roman Leshchinskiy, Johannes Lippmann, Iustin Pop, Steve Purcell, Mitchell Rosen and others. ### Bugs and limitations Bugs can be reported [on the bug tracker](https://github.com/ndmitchell/hlint/issues). There are some issues that I do not intend to fix: * HLint operates on each module at a time in isolation, as a result HLint does not know about types or which names are in scope. * The presence of `seq` may cause some hints (i.e. eta-reduction) to change the semantics of a program. * Some transformed programs may require additional type signatures, particularly if the transformations trigger the monomorphism restriction or involve rank-2 types. * The `RebindableSyntax` extension can cause HLint to suggest incorrect changes. * HLint turns on many language extensions so it can parse more documents, occasionally some break otherwise legal syntax - e.g. `{-#INLINE foo#-}` doesn't work with `MagicHash`. These extensions can be disabled with `-XNoMagicHash`. ## Installing and running HLint Installation follows the standard pattern of any Haskell library or program: type `cabal update` to update your local hackage database, then `cabal install hlint` to install HLint. Once HLint is installed, run `hlint source` where `source` is either a Haskell file, or a directory containing Haskell files. A directory will be searched recursively for any files ending with `.hs` or `.lhs`. For example, running HLint over darcs would give: $ hlint darcs-2.1.2 darcs-2.1.2\src\CommandLine.lhs:94:1: Warning: Use concatMap Found: concat $ map escapeC s Why not: concatMap escapeC s darcs-2.1.2\src\CommandLine.lhs:103:1: Suggestion: Use fewer brackets Found: ftable ++ (map (\ (c, x) -> (toUpper c, urlEncode x)) ftable) Why not: ftable ++ map (\ (c, x) -> (toUpper c, urlEncode x)) ftable darcs-2.1.2\src\Darcs\Patch\Test.lhs:306:1: Warning: Use a more efficient monadic variant Found: mapM (delete_line (fn2fp f) line) old Why not: mapM_ (delete_line (fn2fp f) line) old ... lots more hints ... Each hint says which file/line the hint relates to, how serious an issue it is, a description of the hint, what it found, and what you might want to replace it with. In the case of the first hint, it has suggested that instead of applying `concat` and `map` separately, it would be better to use the combination function `concatMap`. The first hint is marked as an warning, because using `concatMap` in preference to the two separate functions is always desirable. In contrast, the removal of brackets is probably a good idea, but not always. Reasons that a hint might be a suggestion include requiring an additional import, something not everyone agrees on, and functions only available in more recent versions of the base library. **Bug reports:** The suggested replacement should be equivalent - please report all incorrect suggestions not mentioned as known limitations. ### Running with Continuous Integration Before running HLint on your continuous integration (CI) server, you should first ensure there are no existing hints. One way to achieve that is to ignore existing hints by running `hlint . --default > .hlint.yaml` and checking in the resulting `.hlint.yaml`. On the CI you should then run `hlint .` (or `hlint src` if you only want to check the `src` directory). To avoid the cost of compilation you may wish to fetch the [latest HLint binary release](https://github.com/ndmitchell/hlint/releases/latest). For certain CI environments there are helper scripts to do that. **Travis:** Execute the following command: curl -sL https://raw.github.com/ndmitchell/hlint/master/misc/travis.sh | sh -s . The arguments after `-s` are passed to `hlint`, so modify the final `.` if you want other arguments. **Appveyor:** Add the following statement to `.appveyor.yml`: - ps: Invoke-Command ([Scriptblock]::Create((Invoke-WebRequest 'https://raw.githubusercontent.com/ndmitchell/hlint/master/misc/appveyor.ps1').Content)) -ArgumentList @('.') The arguments inside `@()` are passed to `hlint`, so add new arguments surrounded by `'`, space separated - e.g. `@('.' '--report')`. ### Integrations HLint is integrated into lots of places: * Lots of editors have HLint plugins (quite a few have more than one HLint plugin). * HLint is part of the multiple editor plugins [ghc-mod](https://hackage.haskell.org/package/ghc-mod) and [Intero](https://github.com/commercialhaskell/intero). * [Code Climate](https://docs.codeclimate.com/v1.0/docs/hlint) is a CI for analysis which integrates HLint. * [Danger](http://allocinit.io/haskell/danger-and-hlint/) can be used to automatically comment on pull requests with HLint suggestions. ### Automatically Applying Hints By supplying the `--refactor` flag hlint can automatically apply most suggestions. Instead of a list of hints, hlint will instead output the refactored file on stdout. In order to do this, it is necessary to have the `refactor` executable on you path. `refactor` is provided by the [`apply-refact`](https://github.com/mpickering/apply-refact) package, it uses the GHC API in order to transform source files given a list of refactorings to apply. Hlint directly calls the executable to apply the suggestions. Additional configuration can be passed to `refactor` with the `--refactor-options` flag. Some useful flags include `-i` which replaces the original file and `-s` which asks for confirmation before performing a hint. An alternative location for `refactor` can be specified with the `--with-refactor` flag. Simple bindings for [vim](https://github.com/mpickering/hlint-refactor-vim), [emacs](https://github.com/mpickering/hlint-refactor-mode) and [atom](https://github.com/mpickering/hlint-refactor-atom) are provided. There are no plans to support the duplication nor the renaming hints. ### Reports HLint can generate a lot of information, making it difficult to search for particular types of errors. The `--report` flag will cause HLint to generate a report file in HTML, which can be viewed interactively. Reports are recommended when there are more than a handful of hints. ### Language Extensions HLint enables most Haskell extensions, disabling only those which steal too much syntax (currently Arrows, TransformListComp, XmlSyntax and RegularPatterns). Individual extensions can be enabled or disabled with, for instance, `-XArrows`, or `-XNoMagicHash`. The flag `-XHaskell2010` selects Haskell 2010 compatibility. ### Emacs Integration Emacs integration has been provided by [Alex Ott](http://xtalk.msk.su/~ott/). The integration is similar to compilation-mode, allowing navigation between errors. The script is at [hs-lint.el](https://raw.githubusercontent.com/ndmitchell/hlint/master/data/hs-lint.el), and a copy is installed locally in the data directory. To use, add the following code to the Emacs init file: (require 'hs-lint) (defun my-haskell-mode-hook () (local-set-key "\C-cl" 'hs-lint)) (add-hook 'haskell-mode-hook 'my-haskell-mode-hook) ### GHCi Integration GHCi integration has been provided by Gwern Branwen. The integration allows running `:hlint` from the GHCi prompt. The script is at [hlint.ghci](https://raw.githubusercontent.com/ndmitchell/hlint/master/data/hlint.ghci), and a copy is installed locally in the data directory. To use, add the contents to your [GHCi startup file](https://www.haskell.org/ghc/docs/latest/html/users_guide/ghci.html#the-ghci-and-haskeline-files). ### Parallel Operation To run HLint on 4 processors append the flags `-j4`. HLint will usually perform fastest if n is equal to the number of physical processors, which can be done with `-j` alone. If your version of GHC does not support the GHC threaded runtime then install with the command: `cabal install --flags="-threaded"` ### C preprocessor support HLint runs the [cpphs C preprocessor](http://hackage.haskell.org/package/cpphs) over all input files, by default using the current directory as the include path with no defined macros. These settings can be modified using the flags `--cpp-include` and `--cpp-define`. To disable the C preprocessor use the flag `-XNoCPP`. There are a number of limitations to the C preprocessor support: * HLint will only check one branch of an `#if`, based on which macros have been defined. * Any missing `#include` files will produce a warning on the console, but no information in the reports. ## FAQ ### Why are hints not applied recursively? Consider: foo xs = concat (map op xs) This will suggest eta reduction to `concat . map op`, and then after making that change and running HLint again, will suggest use of `concatMap`. Many people wonder why HLint doesn't directly suggest `concatMap op`. There are a number of reasons: * HLint aims to both improve code, and to teach the author better style. Doing modifications individually helps this process. * Sometimes the steps are reasonably complex, by automatically composing them the user may become confused. * Sometimes HLint gets transformations wrong. If suggestions are applied recursively, one error will cascade. * Some people only make use of some of the suggestions. In the above example using concatMap is a good idea, but sometimes eta reduction isn't. By suggesting them separately, people can pick and choose. * Sometimes a transformed expression will be large, and a further hint will apply to some small part of the result, which appears confusing. * Consider `f $ (a b)`. There are two valid hints, either remove the $ or remove the brackets, but only one can be applied. ### Why doesn't the compiler automatically apply the optimisations? HLint doesn't suggest optimisations, it suggests code improvements - the intention is to make the code simpler, rather than making the code perform faster. The [GHC compiler](http://haskell.org/ghc/) automatically applies many of the rules suggested by HLint, so HLint suggestions will rarely improve performance. ### Why doesn't HLint know the fixity for my custom !@%$ operator? HLint knows the fixities for all the operators in the base library, but no others. HLint works on a single file at a time, and does not resolve imports, so cannot see fixity declarations from imported modules. You can tell HLint about fixities by putting them in a hint file, or passing them on the command line. For example, pass `--with=infixr 5 !@%$`, or put all the fixity declarations in a file and pass `--hint=fixities.hs`. You can also use [--find](https://rawgithub.com/ndmitchell/hlint/master/hlint.htm#find) to automatically produce a list of fixity declarations in a file. ### Which hints are used? HLint uses the `hlint.yaml` file it ships with by default (containing things like the `concatMap` hint above), along with with the first `.hlint.yaml` file it finds in the current directory or any parent thereof. To include other hints, pass `--hint=filename.yaml`. If you pass any `--with` hint you will need to explicitly add any `--hint` flags required. ### Why do I sometimes get a "Note" with my hint? Most hints are perfect substitutions, and these are displayed without any notes. However, some hints change the semantics of your program - typically in irrelevant ways - but HLint shows a warning note. HLint does not warn when assuming typeclass laws (such as `==` being symmetric). Some notes you may see include: * __Increases laziness__ - for example `foldl (&&) True` suggests `and` including this note. The new code will work on infinite lists, while the old code would not. Increasing laziness is usually a good idea. * __Decreases laziness__ - for example `(fst a, snd a)` suggests a including this note. On evaluation the new code will raise an error if a is an error, while the old code would produce a pair containing two error values. Only a small number of hints decrease laziness, and anyone relying on the laziness of the original code would be advised to include a comment. * __Removes error__ - for example `foldr1 (&&)` suggests and including the note `Removes error on []`. The new code will produce `True` on the empty list, while the old code would raise an error. Unless you are relying on the exception thrown by the empty list, this hint is safe - and if you do rely on the exception, you would be advised to add a comment. ### What is the difference between error/warning/suggestion? Every hint has a severity level: * __Error__ - by default only used for parse errors. * __Warning__ - for example `concat (map f x)` suggests `concatMap f x` as a "warning" severity hint. From a style point of view, you should always replace a combination of `concat` and `map` with `concatMap`. * __Suggestion__ - for example `x !! 0` suggests `head x` as a "suggestion" severity hint. Typically `head` is a simpler way of expressing the first element of a list, especially if you are treating the list inductively. However, in the expression `f (x !! 4) (x !! 0) (x !! 7)`, replacing the middle argument with `head` makes it harder to follow the pattern, and is probably a bad idea. Suggestion hints are often worthwhile, but should not be applied blindly. The difference between warning and suggestion is one of personal taste, typically my personal taste. If you already have a well developed sense of Haskell style, you should ignore the difference. If you are a beginner Haskell programmer you may wish to focus on warning hints before suggestion hints. ### Is it possible to use pragma annotations in code that is read by `ghci` (conflicts with `OverloadedStrings`)? Short answer: yes, it is! If the language extension `OverloadedStrings` is enabled, `ghci` may however report error messages such as: ``` Ambiguous type variable ‘t0’ arising from an annotation prevents the constraint ‘(Data.Data.Data t0)’ from being solved. ``` In this case, a solution is to add the `:: String` type annotation. For example: ``` {-# ANN someFunc ("HLint: ignore Use fmap" :: String) #-} ``` See discussion in [issue #372](https://github.com/ndmitchell/hlint/issues/372). ## Customizing the hints To customize the hints given by HLint, create a file `.hlint.yaml` in the root of your project. For a suitable default run: hlint --default > .hlint.yaml This default configuration contains lots of examples, including: * Adding command line arguments to all runs, e.g. `--color` or `-XNoMagicHash`. * Ignoring certain hints, perhaps within certain modules/functions. * Restricting use of GHC flags/extensions/functions, e.g. banning `Arrows` and `unsafePerformIO`. * Adding additional project-specific hints. You can see the output of `--default` [here](https://github.com/ndmitchell/hlint/blob/master/data/default.yaml). ### Ignoring hints Some of the hints are subjective, and some users believe they should be ignored. Some hints are applicable usually, but occasionally don't always make sense. The ignoring mechanism provides features for suppressing certain hints. Ignore directives can either be written as pragmas in the file being analysed, or in the hint files. Examples of pragmas are: * `{-# ANN module "HLint: ignore Eta reduce" #-}` - ignore all eta reduction suggestions in this module (use `module` literally, not the name of the module). Put this annotation _after_ the `import` statements. * `{-# ANN myFunction "HLint: ignore" #-}` - don't give any hints in the function `myFunction`. * `{-# ANN myFunction "HLint: error" #-}` - any hint in the function `myFunction` is an error. * `{-# ANN module "HLint: error Use concatMap" #-}` - the hint to use `concatMap` is an error (you may also use `warn` or `suggest` in place of `error` for other severity levels). If you have the `OverloadedStrings` extension enabled you will need to give an explicit type to the annotation, e.g. `{-# ANN myFunction ("HLint: ignore" :: String) #-}`. Ignore directives can also be written in the hint files: * `- ignore: {name: Eta reduce}` - suppress all eta reduction suggestions. * `- ignore: {name: Eta reduce, within: [MyModule1, MyModule2]}` - suppress eta reduction hints in the `MyModule1` and `MyModule2` modules. * `- ignore: {within: MyModule.myFunction}` - don't give any hints in the function `MyModule.myFunction`. * `- error: {within: MyModule.myFunction}` - any hint in the function `MyModule.myFunction` is an error. * `- error: {name: Use concatMap}` - the hint to use `concatMap` is an error (you may also use `warn` or `suggest` in place of `error` for other severity levels). These directives are applied in the order they are given, with later hints overriding earlier ones. ### Adding hints The hint suggesting `concatMap` can be defined as: - warn: {lhs: concat (map f x), rhs: concatMap f x} This line can be read as replace `concat (map f x)` with `concatMap f x`. All single-letter variables are treated as substitution parameters. For examples of more complex hints see the supplied `hlint.yaml` file in the data directory. This hint will automatically match `concat . map f` and `concat $ map f x`, so there is no need to give eta-reduced variants of the hints. Hints may tagged with `error`, `warn` or `suggest` to denote how severe they are by default. In addition, `hint` is a synonym for `suggest`. If you come up with interesting hints, please submit them for inclusion. You can search for possible hints to add from a source file with the `--find` flag, for example: $ hlint --find=src/Utils.hs -- hints found in src/Util.hs - warn: {lhs: "null (intersect a b)", rhs: "disjoint a b"} - warn: {lhs: "dropWhile isSpace", rhs: "trimStart"} - fixity: "infixr 5 !:" These hints are suitable for inclusion in a custom hint file. You can also include Haskell fixity declarations in a hint file, and these will also be extracted. If you pass only `--find` flags then the hints will be written out, if you also pass files/folders to check, then the found hints will be automatically used when checking. ### More Advanced Hints Hints can specify more advanced aspects, with names and side conditions. To see examples and descriptions of these features look at [the default hint file](https://github.com/ndmitchell/hlint/blob/master/data/hlint.yaml) and [the hint interpretation module comments](https://github.com/ndmitchell/hlint/blob/master/src/Hint/Match.hs). ## Hacking HLint Contributions to HLint are most welcome, following [my standard contribution guidelines](https://github.com/ndmitchell/neil/blob/master/README.md#contributions). You can run the tests either from within a `ghci` session by typing `:test` or by running the standalone binary's tests via `stack exec hlint test`. New tests for individual hints can be added directly to source and hint files by adding annotations bracketed in `` code comment blocks. As some examples: ```haskell {- Tests to check the zipFrom hint works zip [1..length x] x -- zipFrom 1 x zip [1..length y] x zip [1..length x] x -- ??? @Warning -} ``` The general syntax is `lhs -- rhs` with `lhs` being the expression you expect to be rewritten as `rhs`. The absence of `rhs` means you expect no hints to fire. In addition `???` lets you assert a warning without a particular suggestion, while `@` tags require a specific severity -- both these features are used less commonly. hlint-2.0.11/LICENSE0000644000000000000000000000276413210071537012114 0ustar0000000000000000Copyright Neil Mitchell 2006-2017. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Neil Mitchell nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hlint-2.0.11/hlint.cabal0000644000000000000000000000606513210071537013207 0ustar0000000000000000cabal-version: >= 1.18 build-type: Simple name: hlint version: 2.0.11 license: BSD3 license-file: LICENSE category: Development author: Neil Mitchell maintainer: Neil Mitchell copyright: Neil Mitchell 2006-2017 synopsis: Source code suggestions description: HLint gives suggestions on how to improve your source code. homepage: https://github.com/ndmitchell/hlint#readme bug-reports: https://github.com/ndmitchell/hlint/issues data-dir: data data-files: hlint.yaml default.yaml Test.hs report_template.html hs-lint.el hlint.1 hlint.ghci HLint_QuickCheck.hs HLint_TypeCheck.hs extra-doc-files: README.md CHANGES.txt tested-with: GHC==8.2.1, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2 source-repository head type: git location: https://github.com/ndmitchell/hlint.git flag threaded default: True manual: True description: Build with support for multithreaded execution flag gpl default: True manual: True description: Use GPL libraries, specifically hscolour library default-language: Haskell2010 build-depends: base == 4.*, process, filepath, directory, containers, unordered-containers, yaml, vector, text, bytestring, transformers, data-default, cpphs >= 1.20.1, cmdargs >= 0.10, haskell-src-exts >= 1.18 && < 1.20, haskell-src-exts-util >= 0.2.1.2, uniplate >= 1.5, ansi-terminal >= 0.6.2, extra >= 1.4.9, refact >= 0.3, aeson >= 1.1.2.0 if flag(gpl) build-depends: hscolour >= 1.21 else cpp-options: -DGPL_SCARES_ME hs-source-dirs: src exposed-modules: Language.Haskell.HLint Language.Haskell.HLint3 other-modules: Paths_hlint Apply CmdLine Grep HLint HsColour Idea Report Util Parallel Refact CC Config.Compute Config.Haskell Config.Read Config.Type Config.Yaml HSE.All HSE.Match HSE.Reduce HSE.Scope HSE.Type HSE.Unify HSE.Util Hint.All Hint.Bracket Hint.Comment Hint.Duplicate Hint.Export Hint.Extensions Hint.Import Hint.Lambda Hint.List Hint.ListRec Hint.Match Hint.Monad Hint.Naming Hint.NewType Hint.Pattern Hint.Pragma Hint.Restrict Hint.Type Hint.Unsafe Hint.Util Test.All Test.Annotations Test.InputOutput Test.Proof Test.Translate Test.Util executable hlint default-language: Haskell2010 build-depends: base, hlint main-is: src/Main.hs ghc-options: -rtsopts if flag(threaded) ghc-options: -threaded hlint-2.0.11/CHANGES.txt0000644000000000000000000006224713210071537012722 0ustar0000000000000000Changelog for HLint 2.0.11 #411, parse the YAML file with lots of HSE extensions turned on #408, use the same config file logic in argsSettings as in hlint Don't use unexported type synonyms in the public API #405, fix false positives on MagicHash due to unboxed literals 2.0.10 #377, suggest lambda case Add CodeClimate support #378, suggest map for degenerate foldr #395, suggest x $> y from x *> pure y and x *> return y #395, suggest x <$ y from pure x <* y and return x <* y #393, suggest f <$> m from m >>= pure . f and pure . f =<< #366, avoid the github API for prebuilt hlint, is rate limited #352, suggest maybe for fromMaybe d (f <$> x) #338, warn about things imported hidden but not used #337, add --git flag to additionally check files in git #353, suggest _ <- mapM to mapM_ #357, warn on unnecessary use of MagicHash 2.0.9 #346, don't suggest explicit export lists #344, fix the API so it works with hlint.yaml by default 2.0.8 #342, add back support for - to mean stdin 2.0.7 #340, fix for directory arguments in the .hlint.yaml 2.0.6 Do statements are not redundant if they change an operator parse #333, simplify labels on Parse error, makes it easier to ignore 2.0.5 If the datadir is missing use data/ relative to the executable Fix test mode to obey --datadir 2.0.4 --default adds ignores for any warnings it finds 2.0.3 #312, suggest removing the DeriveAnyClass extension Suggest removing the DeriveLift extension Remove redundant parts from list comprehensions, e.g. [a | True] #326, fix up the bounds on the eta-reduce hint 2.0.2 #323, try and avoid malformatted JSON #324, use `backticks` in notes #324, remove double escaping in note for --json #322, fix the YAML syntax in default.yaml 2.0.1 #320, still read ./HLint.hs if it exists 2.0 #319, add a hint \x -> f <$> g x ==> fmap f . g Don't say how many hints were ignored Add a --default flag #314, allow arguments in YAML configuration files Add maybe False (== x) ==> (== Just x) hint, plus for /= Remove the ability to pass the file on stdin using - as the file Remove encoding from ParseFlags Remove the --encoding/--utf8 options, always use UTF8 #311, suggest writeFile instead of withFile/hPutStr #288, add configurable restrictions Suggest using an export list on modules Look for nearby .hlint.yaml files to supply configuration Support YAML configuration files #308, don't suggest newtype for unboxed types Remove the import "hint" feature for hint inclusion Builtin hints do not need to be imported, can only be ignored Delete HLint2 API #290, add hints suggesting traverse/traverse_ #303, detect unused OverloadedStrings extension #302, detect unused TupleSections extension 1.9.41 #299, warn in some cases when NumDecimals extension is unused #300, warn when LambdaCase extension is unused #301, when suggesting newtype remove strictness annotations #297, better testing that there isn't a performance regression #167, add -j flag for number of threads #292, add fst/snd . unzip ==> map fst/snd Don't suggest module export trick, breaks Haddock 1.9.40 #293, fix the JSON format of the output 1.9.39 #287, don't incorrectly suggest newtype 1.9.38 #279, suggest newtype instead of data #262, add rules to detect redundant castPtr calls Detect unused TypeApplications extension #277, don't enable TypeApplications extension by default Allow haskell-src-exts-1.19 #276, remove multiple redundant parens in one go #160, add a --only CLI option #237, fix incorrect quasi quotes extension warning #257, better bang pattern hints 1.9.37 #255, don't suggest id @Int ==> @Int #252, avoid clashes with GHCJS in the interim 1.9.36 Require haskell-src-exts-1.18 #249, suggest avoiding elem on singletons 1.9.35 #245, fix parse error reports #243, update hlint.ghci to work with modern GHC Require extra-1.4.9 1.9.34 #154, fix some incorrect line numbers in literate Haskell #161, fix literate Haskell and CPP 1.9.33 #240, remove type-incorrect "on" hint #234, warn about join seq #232, suggest <|> instead of mplus in a few cases 1.9.32 #53, require cpphs-1.20.1, has important fixes #224, treat select $ specially, as per esqueleto conventions #231, don't modify qualification on substitutions #229, add void/mapM_/forM_ hints 1.9.31 #222, don't suggest removing ~ if the Strict extension is on 1.9.30 #220, fix incorrect hints of foldr/foldl on a tuple accumulator 1.9.29 #219, add warnings about foldable methods on tuple Put warnings before suggestions in the HTML report 1.9.28 #215, spot newtype deriving inside classes 1.9.27 #203, avoid recompiling everything twice #213, don't suggest removing bang patterns on let Rename HintStructure to HintPattern #208, add an hlint function to the HLint3 API #1, warn about unused DefaultSignatures extension #137, add -XHaskell2010 and fix -XHaskell98 Allow checking even if a function has different arities #193, don't warn on a -> (b -> c), it's sometimes sensible #182, make parse errors severity Error #181, warn on otherwise in a pattern variable #163, eta reduce fun x = f $ x #132, don't ever suggest liftM #99, downgrade built in hints, Error => Warning => Suggestion #99, add a Suggestion level severity #207, make sure you close file handles #205, add hint compare x y == EQ and /= #204, add hint concatMap id ==> concat #202, include refactorings is --json output 1.9.26 #200, fix all lint warnings #143, expose argsSettings 1.9.25 #192, fix stdin output and --refactor 1.9.24 #188, improve spotting redundant brackets around patterns #138, reenable redundant where hint 1.9.23 #184, require haskell-src-exts-1.17 #183, allow test_ as a prefix 1.9.22 Don't suggest redundant lambda on view patterns Add --no-exit-code flag #174, don't suggest string literals #175, disable 'rec' stealing extensions by default #170, add hints for eta-reduced operators #149, integrate a --refactor flag #147, fix the -fglasgow-exts hint #140, better name for moving brackets to eliminate $ Extra hints for <$> Remove a redundant fmap hint #131, add =<< rules in addition to >>= 1.9.21 #130, ignore a BOM if it exists #128, don't find files starting with . when searching directories Suggest concat even if the [] is written "" 1.9.20 #122, fix the zipWith/repeat hint 1.9.19 #119, don't remove RecursiveDo if they use the rec statement Add a suggestion concatMap/map ==> concatMap 1.9.18 More GHC 7.10 warnings and build support 1.9.17 #116, support hscolour-1.21 1.9.16 #108, make "hlint ." work again 1.9.15 #106, avoid warnings with GHC 7.10 #105, build with GHC 7.10 1.9.14 #649, don't suggest const for values using RecordWildCards 1.9.13 #97, remove the encoding bits of the API #98, add an HLint3 prototype API #93, make the --quickcheck tests work on GHC 7.8 Add --tempdir flag to the test mode 1.9.12 #96, fix the --utf8 flag Make Encoding an alias for TextEncoding Default to UTF8 encoding 1.9.11 #95, don't suggest camel case for names containing digits Add a dependency on the extra package #92, use a new way for determining the color default Add a dependency on ansi-terminal 1.9.10 Spot unsafePerformIO without NOINLINE 1.9.9 #89, fix compiling the executable with --flag=-gpl 1.9.8 #82, don't crash on XmlHybrid modules #88, allow avoiding HsColour, as it is GPL licensed #87, don't push if down, since it can be type incorrect 1.9.7 #86, don't use color unless $TERM claims to support it 1.9.6 #85, fix the free variable matching check for lambda #84, suggest fmap for Either Make --json put each hint on a different line Support -X for extensions to the hse mode 1.9.5 Remove support for GHC 7.2 Upgrade to haskell-src-exts-1.16 1.9.4 #81, fixes for GHC 7.9 #78, add hints for list patterns #72, make --color the default on Linux 1.9.3 #73, fix multithreading and exceptions 1.9.2 #68, add --no-summary 1.9.1 #65, add flip (>>=) ==> (=<<) and the reverse #61, add --json flag 1.9 Remove not (isControl x) ==> isPrint (not true for '\173') #57, warn on invalid pragmas Make the API pass and require comments #59, make sure qualified operators match properly Rename notTypeSafe annotation to noTypeCheck Remove an invalid rule suggesting tanh #13, add a --quickcheck flag to test the hints Add --typecheck flag to test mode to type check the hints Remove incorrect for intercalate to unlines #37, remove incorrect hint for isAlphaNum #45, add mapMaybe id ==> catMaybes #42, add some repeat hints 1.8.61 #40, allow haskell-src-exts-1.15 Don't detect redundant Generics extension 1.8.60 #33, add --cpp-file to preinclude a file #34, add back --quiet flag #639, don't suggest evaluate, because not all Monad's are IO #31, delete the elem/notElem hints #30, remove weird "free module" matching #15, add prototype grep mode Change to make test a separate mode #12, more list based suggestions #637, turn off QuasiQuotes by default 1.8.59 #27, fix up directory file searching 1.8.58 Move the API to Language.Haskell.HLint2 #638, ensure $! doesn't break strictness with strict fields #24, don't remove DeriveFunctor even when only on a newtype #22, turn off UnboxedTuples by default #21, strip /* C style */ comments #635 and #18, require cpphs-1.18.1 Switch to CmdArgs for command line parsing Remove -x as a synonym for --cross 1.8.57 #6, add a preview of an API #331, improve parse error locations for literate Haskell 1.8.56 Remove support for GHC 6.12 and below #317, tone down the void hint #16, match not . not (and reverse . reverse etc) Suggest <$> instead of fmap f $ ... Tweak some priorities, make >=> a warn and void an error #3, make top of the file ANN pragmas work #10, add a suggestion to use unlines #11, add a few hints about characters #8, add CHANGES.txt to the Cabal package 1.8.55 #627, fix the UnboxedTuples extension warning 1.8.54 Fix a bug when suggesting const 1.8.53 Fix some corner cases when suggesting foldr etc. #517, don't introduce new free variables in a replacement 1.8.52 #2, Generic is not newtype derivable 1.8.51 Upgrade to haskell-src-exts-1.14 1.8.50 Eliminate upper bounds on all dependencies #617, fix up notIn to take account of Template Haskell variables #573, suggest removing various deriving language extensions 1.8.49 Remove ^^ ==> ** hint Remove a duplicate sqrt hint Ensure that --test failures throws an error Fix up the copyright year in --help 1.8.48 Brackets at the root of annotations are fine Reduce a few more lambda expressions 1.8.47 #613, compatibility with base-4.7 1.8.46 Remove incorrect isPrefixOf hints #586, add span/break/takeWhile/dropWhile hints #588, add sort/reverse hints #601, add replicate/map/repeat hints Add a hint about reverse/reverse Add side as an alias for _ Add hint as an alias for error 1.8.45 #600, hints for unnecessary lazy annotations 1.8.44 #598, warn on unnecessary bang patterns 1.8.43 Change some hint error/warning levels 1.8.42 Allow cpphs-1.16 1.8.41 #586, add a rule for takeWhile/dropWhile ==> span #522, add hints for the state monad #499, fix up the test suite Fix the side conditions for the `isPrefixOf` hint Add hints about take/drop on non positive numbers Add isNat/isPos/isNeg/isNegPos as notes Make the notes a structured type Add --proof feature Retire the Prelude.catch hint Additional boolean equality hints 1.8.40 #585, lots of additional list based hints 1.8.39 #582, don't suggest renaming with trailingHashes# 1.8.38 #578, treat _ bindings differently in lambdas 1.8.37 #575, allow cpphs-1.15 1.8.36 Make --with imply no default Hint files 1.8.35 #567, avoid duplicate hints around (.) hints 1.8.34 Switch license from GPL to BSD3 1.8.33 Lots more hints on laziness, foldable and a few others Use mapM_ etc in more situations, when using explicit >>= 1.8.32 Add notes about how to deal with imported fixites Add a --with flag for passing settings on the command line #563, make sure TypeSig hints get the right function name Update the copyright year to 2012 #564, allow brackets and type signatures on annotations Add a note that about using !! if the index is negative 1.8.31 Avoid incomplete patterns when reading ANN pragmas #555, top-level expressions require TemplateHaskell 1.8.30 Add elemIndex/elemIndices hints Allow cpphs-1.14 #551, allow case_ as a name with an underscore 1.8.29 Allow hscolor-1.20.* #574, add a hint to for mapM/zip ==> zipWithM 1.8.28 Fix a bug, >=> hint was missing check about removal of free var 1.8.27 Allow haskell-src-exts-1.13.* 1.8.26 Allow haskell-src-exts-1.12.* Don't suggest redundant brackets when turning ++ into : Add hints suggesting >=> and <=< 1.8.25 Update the copyright year in the Cabal file Allow transformers-0.3.* 1.8.24 #531, Make hlint.ghci well formed again 1.8.23 Add hints for redundant seq/evaluate using isWHNF #526, don't hint for return $! (x :: Int) 1.8.22 Add hint for $! where the RHS is not a variable 1.8.21 #508, add lots of hints from the base library #317, add hints for a >> return () to void Add a fromMaybe/fmap ==> maybe hint #304, don't backet tuple sections Add foldl (++) [] ==> concat #512, detect unnecessary case construct When finding hints, don't abort on a parse error #507, add exitSuccess hint #505, suggest record patterns 1.8.20 #500, make sure eta reduction has position information 1.8.19 #498, eta reduce even if there is a where block #497, don't produce an incorrect lambda when suggesting flip 1.8.18 #438, use Foo.Bar to mean Foo/Bar.hs Add a --path command line option to say where files live #441, avoid bad matches due to automatically eta reducing rules #489, import Foo as Foo is redundant #481, suggest liftM instead of fmap when using the Monad laws 1.8.17 #479, allow - as the file to specify using stdin 1.8.16 #478, allow cpphs-1.13.1 Never suggest view patterns (they aren't sufficiently better) Don't suggest use of Data.Ord.comparing, using `on` is better Only suggest elem/notElem on 3 or more items 1.8.15 Add --cpp-ansi to turn on ANSI compat in cpphs 1.8.14 #455, GHC 7.2 compatibility Add lots of hints from Lennart Augustsson 1.8.13 #302, add a backup fixity analysis, if the HSE one fails Fix x /= y || x /= z ==> x `notElem` [y,z], should be && 1.8.12 Allow cpphs-1.12 1.8.11 #440, suggest removing redundant brackets under do #439, don't add redundant brackets under do 1.8.10 Upgrade to hscolour-1.19 1.8.9 #436, add a hint about mapMaybe/map Upgrade to haskell-src-exts-1.11.1 Add a --cross flag, to detect hints between multiple modules #428, don't suggest using String in an instance head 1.8.8 #384, suggest collapsing multiple imports/exports #374, don't suggest the removal of necessary brackets #337, suggest Control.Exception.catch instead of Prelude.catch #412, add hints based on Control.Exception #378, suggest removing fromInteger/fromIntegral on literals #369, add notes to a few hints about possible pitfalls #409, fix a few cases where definitions suggested themselves #410, Support test* as ignored items in settings files #414, add isLit* pattern, and hint about ^^ ==> ** #420, make the suggestion to use let a warning #408, rework the when/unless hints, don't suggest on itself Add duplicate detector, for copy/pasted code #285, don't show duplicate filepath separators If the user enters directories containing no files then say Make suggesting curry/uncurry a warning instead of an error 1.8.7 Relax the transformers dependency, works with 0.0.* and 0.1.* 1.8.6 Export suggestionSeverity/Severity from the API Allow hint imports with "hlint", as well as the existing "hint" 1.8.5 Update the copyright year to 2011 #400, support more encoding strings, give useful errors #401, rename the report template to report_template.html Replace filter f x /= [] with any f x, and 2 more similar 1.8.4 #308, allow haskell-src-exts-1.10.1, which parses Unicode better import qualified Char ==> import qualified Data.Char as Char #393, fix suggestion for import IO, requires more than System.IO #376, note that RecordWildCards implies DisambiguateRecordFields 1.8.3 Allow uniplate-1.6 Switch from mtl to transformers #373, require haskell-src-exts-1.9.6 Add a type signature for GHC 7 Suggest [x | x <- xs] ==> xs, if x is a variable 1.8.2 #371, foo (\x -> y :: Int -> Int) is not a redundant bracket Add a hint to use just equality rather than isJust/fromJust 1.8.1 Massive speed up for files with many naming hints #361, keep module names when suggesting infix Add support for wildcard matching on module names #357, don't camel case suggest on FOO_A #370, fix building with GHC 6.10.4 #313, upgrade to haskell-src-exts-1.9.4 Workaround for #358, disable empty where hints #355, make "--ignore=Parse error" work Add --cpp-simple to run a simple CPP to strip lines begining # Add bracketing information if the parent is a case Suggest intercalate 1.8 Make --test --hint=file typecheck a file for valid hints #347, Suggest use of otherwise, instead of True, in pattern guards Add hints about redundant where statements Suggest removal of redundant guards Make hints about guards work on patterns/infix matches/case alts Make finding guards look a child functions Correctly collapse functions and lambdas using the same patterns Suggest promoting patterns bound to lambdas to functions Allow collapsing lambdas sharing pattern variables correctly #344, only give one warning for multiple collapsable lambdas #300, substantially improve module name resolution with imports BREAKING: imports in hint files require import "hint" HintFile #335, redundant id should only generate one warning Add a hint for using map (f &&& g) #328, for foo'bar suggest the naming fooBar #323, detect redundant brackets in field declarations #321, force the whole file before displaying a parse error Make --find more robust, fixes a potential parse error 1.7.3 Upgrade to hscolour-1.17 1.7.2 #318, match rules by expanding out (.) #319, don't remove lambdas on the right of infix operators 1.7.1 Add a --quiet flag, to supress stdout (mainly for API users) 1.7 Add support for HLint.Builtin.All Fix crash on (\x -> x) Make the library correctly honour the data directory Improve the manual, mainly language changes and hyperlinking Fix a bug in ListRec, could have _recursive_ in the result #315, spot list rec hints through $ and let Add hints based on (f $) ==> f, and change in ListRec hints Changes to the lambda suggestions, now gives a few more hints Don't suggest importing modules in old-locale/old-time Make the API return the suggestions, rather than just the count #278, add -XNoCpp to disable the C preprocessor #279, add -XExt/-XNoExt to choose extensions Remove some redundant brackets in type replacements #286, remove redundant brackets in match Additional bracket removal, application under sections #299, rework hints to use flip (suggest infix in some cases) Add some fromMaybe hints Fix bug where hints didn't always get names #306, make --find use the hints if there are files specified Upgrade to haskell-src-exts-1.9 #303, allow fixities to be specified in hint files 1.6.21 #287, warn about Haskell 98 imports #297, add a hint to use mplus #288, detect redundant brackets under a lambda #302, remove error about ambiguous fixities #281, enhance the redundant monad return warnings #293, eliminate _noParen_ from the result #284, eliminate ViewPatterns from FindHints, hits compiler bug #283, don't suggest removal of RecordWildCards Add some hints about concat and (++) #273, require haskell-src-exts >= 1.8.2 1.6.20 #275, add more acknowledgements (still very incomplete) #254, remove the foldr1/map hint Compress nested lambdas, \x -> \y -> ... ==> \x y -> ... Fix minor bug on \x -> \x -> foo x x #274, add redundant bracket inside record update/construct #272, don't mess up creating sections from qualified names Add some hints to suggest elem Add Paths_hlint to the .cabal file, or the library doesn't link #271, rewrite the match engine in terms of SYB 1.6.19 #251, add automatic definition hunting with --find #268, rewrite the (.) expansion in hints to fix various bugs #269, replacing a case with an if should generate one hint Document the ANN pragmas Require haskell-src-exts-1.8.1 1.6.18 Remove a hint replacing do x <- foo; bar x with foo >>= bar #263, support CPP files more fully Upgrade to hscolour-1.16 Upgrade to cpphs-1.11 1.6.17 Force cpphs-1.10, since 1.11 breaks the interface More hints from the Data.Maybe module #262, add support for the TupleSections extension #264, upgrade to haskell-src-exts-1.8.*, fixes QuasiQuote pos Upgrade to cpphs 1.10 #266, don't match hints that appear to be the definitions #248, tone down the eta reduction hints Add support for WARNING pragma's to reclassify hints Support ignoring hints on types Give better error messages on incorrect settings files Add temporary haskell-src-exts 1.5/1.6 compatibility #327, add hints to use expressions infix #240, if a then True else False no longer suggests a || False Upgrade to haskell-src-exts-1.7.* #236, support changing the text encoding with --encoding/--utf8 #260, generate nicer lambdas for (($) . f) Add the hint (($) . f) ==> (f $) 1.6.16 Further performance enhancements (for details see my blog) Update to uniplate 1.5.* (fixes performance bug) Improve speed based on profiling (roughly twice as fast) #245, add hints for excess brackets in types and patterns Make 100% redundant brackets an error Fix bug where qualified names did not match Remove dependency on SYB #234, allow TH top-level splices for ignore #110, add tests for ignoring commands 1.6.15 Upgrade to uniplate 1.4.* (fixes performance bug) #192, make HLint into a fairly basic library Add --datadir to allow running with a different data directory #254, eliminate foldl/map fusion rules (which were untrue) Fix a few typos in the hint rules Upgrade to uniplate 1.3.* Upgrade to haskell-src-exts 1.6.* Add a .ghci file snippet #247, Fix bug matching expressions containing position info 1.6.14 Upgrade to haskell-src-exts 1.5.* 1.6.13 #246, redundant brackets in [(...)] Add fold/map fusion hints Don't suggest namings that are already used in the module #239, Add suggestions of and/or on foldl Add --extension flag, to find files not named .hs/.lhs Only activate the builtin hints when they are imported Fix matching bug, said "Use flip" on "\v -> f v . g" Suggest changing some pattern guards to view patterns 1.6.12 Fix a bug with ignored hints being written to reports Upgrade to haskell-src-exts 1.3.* #228, suggest let instead of <- return in do statements #229, suggest comparing Qualify all non-Prelude function suggestions #225, Add redundant flip hint #226, Add ((+) x) ==> (x +) #223, TemplateHaskell may allow other extensions via code Fix incorrect suggestion on do x <- f ; g x x A few small additional hints (use flip, redundant id) 1.6.11 Don't perform type eta reduction 1.6.10 Fix bug, eta reduction on chained infix operators, i.e. x#y#z 1.6.9 #217, don't suggest eta reduction on - or + Fix bug, PatternGuards under case alternatives were ignored 1.6.8 #213, upgrade to cpphs 1.9 Add suggestion to replace lambda with operator sections Fix bug, ''Name decided TemplateHaskell was unnecessary HPC statistics, and increase in test coverage Fix bug, import A as Y; import A gave import A, missing the as Y Fix bug, type Foo a = Bar a a incorrectly suggested eta reduce 1.6.7 NOTE: #213 has not been fixed, cpphs can cause hangs Add threaded flag to Cabal to disable -threaded mode #212, fix crash Fix bug, incorrectly decided TemplateHaskell was unnecessary 1.6.6 Upgrade to hscolour 1.15 Add a hint for using unless #211, add hints for unused extensions #188, add pragma hints Add a few additional hints (Functor laws) #137, add cpphs support #189, give hints for redundant imports Upgrade to haskell-src-exts 1.1.* 1.6.5 #206, better presentation of parse errors #208, give the correct precedence to ==> in source files 1.6.4 Start of changelog hlint-2.0.11/src/0000755000000000000000000000000013210071537011665 5ustar0000000000000000hlint-2.0.11/src/Util.hs0000644000000000000000000000416113210071537013140 0ustar0000000000000000{-# LANGUAGE ExistentialQuantification, Rank2Types #-} module Util( defaultExtensions, gzip, universeParentBi, exitMessage, exitMessageImpure ) where import Data.List import System.Exit import System.IO import System.IO.Unsafe import Unsafe.Coerce import Data.Data import Data.Generics.Uniplate.Operations import Language.Haskell.Exts.Extension --------------------------------------------------------------------- -- SYSTEM.IO exitMessage :: String -> IO a exitMessage msg = do hPutStrLn stderr msg exitWith $ ExitFailure 1 exitMessageImpure :: String -> a exitMessageImpure = unsafePerformIO . exitMessage --------------------------------------------------------------------- -- DATA.GENERICS data Box = forall a . Data a => Box a gzip :: Data a => (forall b . Data b => b -> b -> c) -> a -> a -> Maybe [c] gzip f x y | toConstr x /= toConstr y = Nothing | otherwise = Just $ zipWith op (gmapQ Box x) (gmapQ Box y) -- unsafeCoerce is safe because gmapQ on the same constr gives the same fields -- in the same order where op (Box x) (Box y) = f x (unsafeCoerce y) --------------------------------------------------------------------- -- DATA.GENERICS.UNIPLATE.OPERATIONS universeParent :: Uniplate a => a -> [(Maybe a, a)] universeParent x = (Nothing,x) : f x where f :: Uniplate a => a -> [(Maybe a, a)] f x = concat [(Just x, y) : f y | y <- children x] universeParentBi :: Biplate a b => a -> [(Maybe b, b)] universeParentBi = concatMap universeParent . childrenBi --------------------------------------------------------------------- -- LANGUAGE.HASKELL.EXTS.EXTENSION defaultExtensions :: [Extension] defaultExtensions = [e | e@EnableExtension{} <- knownExtensions] \\ map EnableExtension badExtensions badExtensions = [Arrows -- steals proc ,TransformListComp -- steals the group keyword ,XmlSyntax, RegularPatterns -- steals a-b ,UnboxedTuples -- breaks (#) lens operator ,QuasiQuotes -- breaks [x| ...], making whitespace free list comps break ,DoRec, RecursiveDo -- breaks rec ,TypeApplications -- HSE fails on @ patterns ] hlint-2.0.11/src/Report.hs0000644000000000000000000000526613210071537013505 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} module Report(writeReport) where import Idea import Data.Tuple.Extra import Data.List.Extra import Data.Maybe import Data.Version import System.FilePath import System.IO.Extra import HSE.All import Paths_hlint import HsColour writeTemplate :: FilePath -> [(String,[String])] -> FilePath -> IO () writeTemplate dataDir content to = do src <- readFile' $ dataDir "report_template.html" writeFile to $ unlines $ concatMap f $ lines src where f ('$':xs) = fromMaybe ['$':xs] $ lookup xs content f x = [x] writeReport :: FilePath -> FilePath -> [Idea] -> IO () writeReport dataDir file ideas = writeTemplate dataDir inner file where generateIds :: [String] -> [(String,Int)] -- sorted by name generateIds = map (head &&& length) . group -- must be already sorted files = generateIds $ sort $ map (srcSpanFilename . ideaSpan) ideas hints = generateIds $ map hintName $ sortOn (negate . fromEnum . ideaSeverity &&& hintName) ideas hintName x = show (ideaSeverity x) ++ ": " ++ ideaHint x inner = [("VERSION",['v' : showVersion version]),("CONTENT",content), ("HINTS",list "hint" hints),("FILES",list "file" files)] content = concatMap (\i -> writeIdea (getClass i) i) ideas getClass i = "hint" ++ f hints (hintName i) ++ " file" ++ f files (srcSpanFilename $ ideaSpan i) where f xs x = show $ fromJust $ findIndex ((==) x . fst) xs list mode = zipWith f [0..] where f i (name,n) = "
  • " ++ escapeHTML name ++ " (" ++ show n ++ ")
  • " where id = mode ++ show i writeIdea :: String -> Idea -> [String] writeIdea cls Idea{..} = ["
    " ,escapeHTML (showSrcLoc (getPointLoc ideaSpan) ++ ": " ++ show ideaSeverity ++ ": " ++ ideaHint) ++ "
    " ,"Found
    " ,hsColourHTML ideaFrom] ++ (case ideaTo of Nothing -> [] Just to -> ["Why not" ++ (if to == "" then " remove it." else "") ++ "
    " ,hsColourHTML to]) ++ [let n = showNotes ideaNote in if n /= "" then "Note: " ++ writeNote n ++ "" else "" ,"
    " ,""] -- Unescaped, but may have `backticks` for code writeNote :: String -> String writeNote = f . splitOn "`" where f (a:b:c) = escapeHTML a ++ "" ++ escapeHTML b ++ "" ++ f c f xs = concatMap escapeHTML xs escapeHTML :: String -> String escapeHTML = concatMap f where f '>' = ">" f '<' = "<" f '&' = "&" f x = [x] hlint-2.0.11/src/Refact.hs0000644000000000000000000000065713210071537013435 0ustar0000000000000000module Refact(toRefactSrcSpan, toSS) where import qualified Refact.Types as R import HSE.All toRefactSrcSpan :: SrcSpan -> R.SrcSpan toRefactSrcSpan ss = R.SrcSpan (srcSpanStartLine ss) (srcSpanStartColumn ss) (srcSpanEndLine ss) (srcSpanEndColumn ss) toSS :: Annotated a => a S -> R.SrcSpan toSS = toRefactSrcSpan . srcInfoSpan . ann hlint-2.0.11/src/Parallel.hs0000644000000000000000000000231413210071537013755 0ustar0000000000000000{- The parallel function (specialised to lists) is equivalent to: import Control.Parallel.Strategies parallel :: [IO [a]] -> IO [[a]] parallel = return . withStrategy (parList $ seqList r0) . map unsafePerformIO However, this version performs about 10% slower with 2 processors in GHC 6.12.1 -} module Parallel(parallel) where import System.IO.Unsafe import Control.Concurrent import Control.Exception import Control.Monad parallel :: Int -> [IO a] -> IO [a] parallel j = if j <= 1 then parallel1 else parallelN j parallel1 :: [IO a] -> IO [a] parallel1 [] = return [] parallel1 (x:xs) = do x2 <- x xs2 <- unsafeInterleaveIO $ parallel1 xs return $ x2:xs2 parallelN :: Int -> [IO a] -> IO [a] parallelN j xs = do ms <- mapM (const newEmptyMVar) xs chan <- newChan mapM_ (writeChan chan . Just) $ zip ms xs replicateM_ j (writeChan chan Nothing >> forkIO (f chan)) let throwE x = throw (x :: SomeException) parallel1 $ map (fmap (either throwE id) . takeMVar) ms where f chan = do v <- readChan chan case v of Nothing -> return () Just (m,x) -> do putMVar m =<< try x f chan hlint-2.0.11/src/Main.hs0000644000000000000000000000037213210071537013107 0ustar0000000000000000 module Main(main) where import Language.Haskell.HLint3 import Control.Monad import System.Environment import System.Exit main :: IO () main = do args <- getArgs errs <- hlint args unless (null errs) $ exitWith $ ExitFailure 1 hlint-2.0.11/src/Idea.hs0000644000000000000000000000722713210071537013073 0ustar0000000000000000{-# LANGUAGE RecordWildCards, NoMonomorphismRestriction #-} module Idea( Idea(..), rawIdea, idea, suggest, warn, ignore, rawIdeaN, suggestN, showIdeasJson, showANSI, Note(..), showNotes, Severity(..) ) where import Data.List.Extra import Data.Char import Numeric import HSE.All import Config.Type import HsColour import Refact.Types hiding (SrcSpan) import qualified Refact.Types as R -- | An idea suggest by a 'Hint'. data Idea = Idea {ideaModule :: String -- ^ The module the idea applies to, may be @\"\"@ if the module cannot be determined or is a result of cross-module hints. ,ideaDecl :: String -- ^ The declaration the idea applies to, typically the function name, but may be a type name. ,ideaSeverity :: Severity -- ^ The severity of the idea, e.g. 'Warning'. ,ideaHint :: String -- ^ The name of the hint that generated the idea, e.g. @\"Use reverse\"@. ,ideaSpan :: SrcSpan -- ^ The source code the idea relates to. ,ideaFrom :: String -- ^ The contents of the source code the idea relates to. ,ideaTo :: Maybe String -- ^ The suggested replacement, or 'Nothing' for no replacement (e.g. on parse errors). ,ideaNote :: [Note] -- ^ Notes about the effect of applying the replacement. ,ideaRefactoring :: [Refactoring R.SrcSpan] -- ^ How to perform this idea } deriving (Eq,Ord) -- I don't use aeson here for 2 reasons: -- 1) Aeson doesn't esape unicode characters, and I want to (allows me to ignore encoding) -- 2) I want to control the format so it's slightly human readable as well showIdeaJson :: Idea -> String showIdeaJson idea@Idea{ideaSpan=srcSpan@SrcSpan{..}, ..} = wrap . intercalate "," . map mkPair $ [("module", str ideaModule) ,("decl", str ideaDecl) ,("severity", str $ show ideaSeverity) ,("hint", str ideaHint) ,("file", str srcSpanFilename) ,("startLine", show srcSpanStartLine) ,("startColumn", show srcSpanStartColumn) ,("endLine", show srcSpanEndLine) ,("endColumn", show srcSpanEndColumn) ,("from", str ideaFrom) ,("to", maybe "null" str ideaTo) ,("note", "[" ++ intercalate "," (map (str . show) ideaNote) ++ "]") ,("refactorings", str $ show ideaRefactoring) ] where str x = "\"" ++ concatMap f x ++ "\"" where f '\"' = "\\\"" f '\\' = "\\\\" f '\n' = "\\n" f '\r' = "\\r" f x | isControl x || not (isAscii x) = "\\u" ++ takeEnd 4 ("0000" ++ showHex (ord x) "") f x = [x] mkPair (k, v) = show k ++ ":" ++ v wrap x = "{" ++ x ++ "}" showIdeasJson :: [Idea] -> String showIdeasJson ideas = "[" ++ intercalate "\n," (map showIdeaJson ideas) ++ "]" instance Show Idea where show = showEx id showANSI :: IO (Idea -> String) showANSI = do f <- hsColourConsole return $ showEx f showEx :: (String -> String) -> Idea -> String showEx tt Idea{..} = unlines $ [showSrcLoc (getPointLoc ideaSpan) ++ ": " ++ (if ideaHint == "" then "" else show ideaSeverity ++ ": " ++ ideaHint)] ++ f "Found" (Just ideaFrom) ++ f "Why not" ideaTo ++ ["Note: " ++ n | let n = showNotes ideaNote, n /= ""] where f msg Nothing = [] f msg (Just x) | null xs = [msg ++ " remove it."] | otherwise = (msg ++ ":") : map (" "++) xs where xs = lines $ tt x rawIdea = Idea "" "" rawIdeaN a b c d e f = Idea "" "" a b c d e f [] idea severity hint from to = rawIdea severity hint (srcInfoSpan $ ann from) (f from) (Just $ f to) [] where f = trimStart . prettyPrint suggest = idea Suggestion warn = idea Warning ignore = idea Ignore ideaN severity hint from to = idea severity hint from to [] suggestN = ideaN Suggestion hlint-2.0.11/src/HsColour.hs0000644000000000000000000000106013210071537013754 0ustar0000000000000000{-# LANGUAGE CPP #-} module HsColour(hsColourHTML, hsColourConsole) where #ifdef GPL_SCARES_ME hsColourConsole :: IO (String -> String) hsColourConsole = return id hsColourHTML :: String -> String hsColourHTML = id #else import Language.Haskell.HsColour.TTY as TTY import Language.Haskell.HsColour.Colourise import Language.Haskell.HsColour.CSS as CSS hsColourConsole :: IO (String -> String) hsColourConsole = do prefs <- readColourPrefs return $ TTY.hscolour prefs hsColourHTML :: String -> String hsColourHTML = CSS.hscolour False 1 #endif hlint-2.0.11/src/HLint.hs0000644000000000000000000002216213210071537013242 0ustar0000000000000000{-# LANGUAGE RecordWildCards, TupleSections #-} {-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} module HLint(hlint, readAllSettings) where import Control.Applicative import Control.Monad.Extra import Control.Exception import Control.Concurrent.Extra import System.Console.CmdArgs.Verbosity import Data.List.Extra import GHC.Conc import System.Exit import System.IO.Extra import Data.Tuple.Extra import Prelude import Data.Version.Extra import System.Process.Extra import Data.Maybe import System.Directory import System.FilePath import CmdLine import Config.Read import Config.Type import Config.Compute import Report import Idea import Apply import Test.All import Hint.All import Grep import Test.Proof import Parallel import HSE.All import CC -- | This function takes a list of command line arguments, and returns the given hints. -- To see a list of arguments type @hlint --help@ at the console. -- This function writes to the stdout/stderr streams, unless @--quiet@ is specified. -- -- As an example: -- -- > do hints <- hlint ["src", "--ignore=Use map","--quiet"] -- > when (length hints > 3) $ error "Too many hints!" -- -- /Warning:/ The flags provided by HLint are relatively stable, but do not have the same -- API stability guarantees as the rest of the strongly-typed API. Do not run this function -- on a your server with untrusted input. hlint :: [String] -> IO [Idea] hlint args = do cmd <- getCmd args case cmd of CmdMain{} -> do xs <- hlintMain args cmd; return $ if cmdNoExitCode cmd then [] else xs CmdGrep{} -> hlintGrep cmd >> return [] CmdHSE{} -> hlintHSE cmd >> return [] CmdTest{} -> hlintTest cmd >> return [] hlintHSE :: Cmd -> IO () hlintHSE c@CmdHSE{..} = do v <- getVerbosity forM_ cmdFiles $ \x -> do putStrLn $ "Parse result of " ++ x ++ ":" let (lang,exts) = cmdExtensions c -- We deliberately don't use HSE.All here to avoid any bugs in HLint -- polluting our bug reports (which is the main use of HSE) res <- parseFileWithMode defaultParseMode{baseLanguage=lang, extensions=exts} x case res of x@ParseFailed{} -> print x ParseOk m -> case v of Loud -> print m Quiet -> print $ prettyPrint m _ -> print $ void m putStrLn "" hlintTest :: Cmd -> IO () hlintTest cmd@CmdTest{..} = if not $ null cmdProof then do files <- cmdHintFiles cmd s <- readFilesConfig $ map (,Nothing) files let reps = if cmdReports == ["report.html"] then ["report.txt"] else cmdReports mapM_ (proof reps s) cmdProof else do failed <- test cmd (\args -> do errs <- hlint args; unless (null errs) $ exitWith $ ExitFailure 1) cmdDataDir cmdGivenHints when (failed > 0) exitFailure cmdParseFlags :: Cmd -> ParseFlags cmdParseFlags cmd = parseFlagsSetLanguage (cmdExtensions cmd) $ defaultParseFlags{cppFlags=cmdCpp cmd} hlintGrep :: Cmd -> IO () hlintGrep cmd@CmdGrep{..} = if null cmdFiles then exitWithHelp else do files <- concatMapM (resolveFile cmd Nothing) cmdFiles if null files then error "No files found" else runGrep cmdPattern (cmdParseFlags cmd) files withVerbosity :: Verbosity -> IO a -> IO a withVerbosity new act = do old <- getVerbosity (setVerbosity new >> act) `finally` setVerbosity old hlintMain :: [String] -> Cmd -> IO [Idea] hlintMain args cmd@CmdMain{..} | cmdDefault = do ideas <- if null cmdFiles then return [] else withVerbosity Quiet $ runHlintMain args cmd{cmdJson=False,cmdSerialise=False,cmdRefactor=False} Nothing let bad = nubOrd $ map ideaHint ideas src <- readFile $ cmdDataDir "default.yaml" if null bad then putStr src else do let group1:groups = splitOn ["",""] $ lines src let group2 = "# Warnings currently triggered by your code" : ["- ignore: {name: " ++ show x ++ "}" | x <- bad] putStr $ unlines $ intercalate ["",""] $ group1:group2:groups return [] | null cmdFiles && not (null cmdFindHints) = do hints <- concatMapM (resolveFile cmd Nothing) cmdFindHints mapM_ (putStrLn . fst <=< computeSettings (cmdParseFlags cmd)) hints >> return [] | null cmdFiles = exitWithHelp | cmdRefactor = withTempFile $ runHlintMain args cmd . Just | otherwise = runHlintMain args cmd Nothing runHlintMain :: [String] -> Cmd -> Maybe FilePath -> IO [Idea] runHlintMain args cmd tmpFile = do (cmd, settings) <- readAllSettings args cmd runHints args settings =<< resolveFiles cmd tmpFile resolveFiles :: Cmd -> Maybe FilePath -> IO Cmd resolveFiles cmd@CmdMain{..} tmpFile = do files <- concatMapM (resolveFile cmd tmpFile) cmdFiles if null files then error "No files found" else pure cmd { cmdFiles = files } resolveFiles cmd _ = pure cmd readAllSettings :: [String] -> Cmd -> IO (Cmd, [Setting]) readAllSettings args1 cmd@CmdMain{..} = do files <- cmdHintFiles cmd settings1 <- readFilesConfig $ map (,Nothing) files ++ [("CommandLine.hs",Just x) | x <- cmdWithHints] let args2 = [x | SettingArgument x <- settings1] cmd@CmdMain{..} <- if null args2 then return cmd else getCmd $ args1 ++ args2 settings2 <- concatMapM (fmap snd . computeSettings (cmdParseFlags cmd)) cmdFindHints settings3 <- return [SettingClassify $ Classify Ignore x "" "" | x <- cmdIgnore] return (cmd, settings1 ++ settings2 ++ settings3) runHints :: [String] -> [Setting] -> Cmd -> IO [Idea] runHints args settings cmd@CmdMain{..} = do j <- if cmdThreads == 0 then getNumProcessors else return cmdThreads withNumCapabilities j $ do let outStrLn = whenNormal . putStrLn ideas <- getIdeas cmd settings ideas <- return $ if cmdShowAll then ideas else filter (\i -> ideaSeverity i /= Ignore) ideas if cmdJson then putStrLn $ showIdeasJson ideas else if cmdCC then mapM_ (printIssue . fromIdea) ideas else if cmdSerialise then do hSetBuffering stdout NoBuffering print $ map (show &&& ideaRefactoring) ideas else if cmdRefactor then handleRefactoring ideas cmdFiles cmd else do usecolour <- cmdUseColour cmd showItem <- if usecolour then showANSI else return show mapM_ (outStrLn . showItem) ideas handleReporting ideas cmd return ideas getIdeas :: Cmd -> [Setting] -> IO [Idea] getIdeas cmd@CmdMain{..} settings = do settings <- return $ settings ++ map (Builtin . fst) builtinHints let flags = cmdParseFlags cmd ideas <- if cmdCross then applyHintFiles flags settings cmdFiles else concat <$> parallel cmdThreads [evaluateList =<< applyHintFile flags settings x Nothing | x <- cmdFiles] return $ if not (null cmdOnly) then [i | i <- ideas, ideaHint i `elem` cmdOnly] else ideas handleRefactoring :: [Idea] -> [String] -> Cmd -> IO () handleRefactoring ideas files cmd@CmdMain{..} = case cmdFiles of [file] -> do -- Ensure that we can find the executable path <- checkRefactor (if cmdWithRefactor == "" then Nothing else Just cmdWithRefactor) -- writeFile "hlint.refact" let hints = show $ map (show &&& ideaRefactoring) ideas withTempFile $ \f -> do writeFile f hints exitWith =<< runRefactoring path file f cmdRefactorOptions _ -> error "Refactor flag can only be used with an individual file" handleReporting :: [Idea] -> Cmd -> IO () handleReporting showideas cmd@CmdMain{..} = do let outStrLn = whenNormal . putStrLn if null showideas then when (cmdReports /= []) $ outStrLn "Skipping writing reports" else forM_ cmdReports $ \x -> do outStrLn $ "Writing report to " ++ x ++ " ..." writeReport cmdDataDir x showideas unless cmdNoSummary $ do let n = length showideas outStrLn $ if n == 0 then "No hints" else show n ++ " hint" ++ ['s' | n/=1] runRefactoring :: FilePath -> FilePath -> FilePath -> String -> IO ExitCode runRefactoring rpath fin hints opts = do let args = [fin, "-v0"] ++ words opts ++ ["--refact-file", hints] (_, _, _, phand) <- createProcess $ proc rpath args try $ hSetBuffering stdin LineBuffering :: IO (Either IOException ()) hSetBuffering stdout LineBuffering -- Propagate the exit code from the spawn process waitForProcess phand checkRefactor :: Maybe FilePath -> IO FilePath checkRefactor rpath = do let excPath = fromMaybe "refactor" rpath mexc <- findExecutable excPath case mexc of Just exc -> do ver <- readVersion . tail <$> readProcess exc ["--version"] "" if versionBranch ver >= [0,1,0,0] then return exc else error "Your version of refactor is too old, please upgrade to the latest version" Nothing -> error $ unlines [ "Could not find refactor", "Tried with: " ++ excPath ] evaluateList :: [a] -> IO [a] evaluateList xs = do evaluate $ length xs return xs hlint-2.0.11/src/Grep.hs0000644000000000000000000000216313210071537013120 0ustar0000000000000000 module Grep(runGrep) where import Hint.All import Apply import Config.Type import HSE.All import Control.Monad import Data.List import Util import Idea runGrep :: String -> ParseFlags -> [FilePath] -> IO () runGrep patt flags files = do exp <- case parseExp patt of ParseOk x -> return x ParseFailed sl msg -> exitMessage $ (if "Parse error" `isPrefixOf` msg then msg else "Parse error in pattern: " ++ msg) ++ "\n" ++ patt ++ "\n" ++ replicate (srcColumn sl - 1) ' ' ++ "^" let scope = scopeCreate $ Module an Nothing [] [] [] let rule = hintRules [HintRule Suggestion "grep" scope exp (Tuple an Boxed []) Nothing []] forM_ files $ \file -> do res <- parseModuleEx flags file Nothing case res of Left (ParseError sl msg ctxt) -> print $ rawIdeaN Error (if "Parse error" `isPrefixOf` msg then msg else "Parse error: " ++ msg) (mkSrcSpan sl sl) ctxt Nothing [] Right m -> forM_ (applyHints [] rule [m]) $ \i -> print i{ideaHint="", ideaTo=Nothing} hlint-2.0.11/src/CmdLine.hs0000644000000000000000000003430313210071537013537 0ustar0000000000000000{-# LANGUAGE PatternGuards, DeriveDataTypeable #-} {-# OPTIONS_GHC -fno-warn-missing-fields -fno-cse -O0 #-} module CmdLine( Cmd(..), getCmd, CppFlags(..), cmdCpp, cmdExtensions, cmdHintFiles, cmdUseColour, exitWithHelp, resolveFile ) where import Control.Monad.Extra import Data.Char import Data.List import System.Console.ANSI(hSupportsANSI) import System.Console.CmdArgs.Implicit import System.Console.CmdArgs.Explicit(helpText, HelpFormat(..)) import System.Directory.Extra import System.Exit import System.FilePath import System.IO import System.Process(readProcess) import Language.Preprocessor.Cpphs import HSE.All(CppFlags(..)) import Language.Haskell.Exts(defaultParseMode, baseLanguage) import Language.Haskell.Exts.Extension import Data.Maybe import System.Environment.Extra import System.Info.Extra import Util import Paths_hlint import Data.Version getCmd :: [String] -> IO Cmd getCmd args = withArgs (map f args) $ automatic =<< cmdArgsRun mode where f x = if x == "-?" || x == "--help" then "--help=all" else x automatic :: Cmd -> IO Cmd automatic cmd = case cmd of CmdMain{} -> dataDir =<< path =<< git =<< extension cmd CmdGrep{} -> path =<< extension cmd CmdTest{} -> dataDir cmd _ -> return cmd where path cmd = return $ if null $ cmdPath cmd then cmd{cmdPath=["."]} else cmd extension cmd = return $ if null $ cmdExtension cmd then cmd{cmdExtension=["hs","lhs"]} else cmd dataDir cmd | cmdDataDir cmd /= "" = return cmd | otherwise = do x <- getDataDir b <- doesDirectoryExist x if b then return cmd{cmdDataDir=x} else do exe <- getExecutablePath return cmd{cmdDataDir = takeDirectory exe "data"} git cmd | cmdGit cmd = do mgit <- findExecutable "git" case mgit of Nothing -> error "Could not find git" Just git -> do let args = ["ls-files", "--cached", "--others", "--exclude-standard"] ++ map ("*." ++) (cmdExtension cmd) files <- readProcess git args "" return cmd{cmdFiles = cmdFiles cmd ++ lines files} | otherwise = return cmd exitWithHelp :: IO a exitWithHelp = do putStr $ show $ helpText [] HelpFormatAll mode exitSuccess -- | When to colour terminal output. data ColorMode = Never -- ^ Terminal output will never be coloured. | Always -- ^ Terminal output will always be coloured. | Auto -- ^ Terminal output will be coloured if $TERM and stdout appear to support it. deriving (Show, Typeable, Data) instance Default ColorMode where def = if isWindows then Never else Auto data Cmd = CmdMain {cmdFiles :: [FilePath] -- ^ which files to run it on, nothing = none given ,cmdReports :: [FilePath] -- ^ where to generate reports ,cmdGivenHints :: [FilePath] -- ^ which settignsfiles were explicitly given ,cmdWithHints :: [String] -- ^ hints that are given on the command line ,cmdGit :: Bool -- ^ use git ls-files to find files ,cmdColor :: ColorMode -- ^ color the result ,cmdThreads :: Int -- ^ Numbmer of threads to use, 0 = whatever GHC has ,cmdIgnore :: [String] -- ^ the hints to ignore ,cmdShowAll :: Bool -- ^ display all skipped items ,cmdExtension :: [String] -- ^ extensions ,cmdLanguage :: [String] -- ^ the extensions (may be prefixed by "No") ,cmdCross :: Bool -- ^ work between source files, applies to hints such as duplicate code between modules ,cmdFindHints :: [FilePath] -- ^ source files to look for hints in ,cmdDataDir :: FilePath -- ^ the data directory ,cmdDefault :: Bool -- ^ Print a default file to stdout ,cmdPath :: [String] ,cmdCppDefine :: [String] ,cmdCppInclude :: [FilePath] ,cmdCppFile :: [FilePath] ,cmdCppSimple :: Bool ,cmdCppAnsi :: Bool ,cmdJson :: Bool -- ^ display hint data as JSON ,cmdCC :: Bool -- ^ display hint data as Code Climate Issues ,cmdNoSummary :: Bool -- ^ do not show the summary info ,cmdOnly :: [String] -- ^ specify which hints explicitly ,cmdNoExitCode :: Bool ,cmdSerialise :: Bool -- ^ Display hints in serialisation format ,cmdRefactor :: Bool -- ^ Run the `refactor` executable to automatically perform hints ,cmdRefactorOptions :: String -- ^ Options to pass to the `refactor` executable. ,cmdWithRefactor :: FilePath -- ^ Path to refactor tool } | CmdGrep {cmdFiles :: [FilePath] -- ^ which files to run it on, nothing = none given ,cmdPattern :: String ,cmdExtension :: [String] -- ^ extensions ,cmdLanguage :: [String] -- ^ the extensions (may be prefixed by "No") ,cmdPath :: [String] ,cmdCppDefine :: [String] ,cmdCppInclude :: [FilePath] ,cmdCppFile :: [FilePath] ,cmdCppSimple :: Bool ,cmdCppAnsi :: Bool } | CmdTest {cmdProof :: [FilePath] -- ^ a proof script to check against ,cmdGivenHints :: [FilePath] -- ^ which settings files were explicitly given ,cmdDataDir :: FilePath -- ^ the data directory ,cmdReports :: [FilePath] -- ^ where to generate reports ,cmdWithHints :: [String] -- ^ hints that are given on the command line ,cmdTempDir :: FilePath -- ^ temporary directory to put the files in ,cmdQuickCheck :: Bool ,cmdTypeCheck :: Bool } | CmdHSE {cmdFiles :: [FilePath] ,cmdLanguage :: [String] -- ^ the extensions (may be prefixed by "No") } deriving (Data,Typeable,Show) mode = cmdArgsMode $ modes [CmdMain {cmdFiles = def &= args &= typ "FILE/DIR" ,cmdReports = nam "report" &= opt "report.html" &= typFile &= help "Generate a report in HTML" ,cmdGivenHints = nam "hint" &= typFile &= help "Hint/ignore file to use" ,cmdWithHints = nam "with" &= typ "HINT" &= help "Extra hints to use" ,cmdGit = nam "git" &= help "Run on files tracked by git" ,cmdColor = nam "colour" &= name "color" &= opt Always &= typ "always/never/auto" &= help "Color output (requires ANSI terminal; auto means on when $TERM is supported; by itself, selects always)" ,cmdThreads = 1 &= name "threads" &= name "j" &= opt (0 :: Int) &= help "Number of threads to use (-j for all)" ,cmdIgnore = nam "ignore" &= typ "HINT" &= help "Ignore a particular hint" ,cmdShowAll = nam "show" &= help "Show all ignored ideas" ,cmdExtension = nam "extension" &= typ "EXT" &= help "File extensions to search (default hs/lhs)" ,cmdLanguage = nam_ "language" &= name "X" &= typ "EXTENSION" &= help "Language extensions (Arrows, NoCPP)" ,cmdCross = nam_ "cross" &= help "Work between modules" ,cmdFindHints = nam "find" &= typFile &= help "Find hints in a Haskell file" ,cmdDataDir = nam_ "datadir" &= typDir &= help "Override the data directory" ,cmdDefault = nam "default" &= help "Print a default file to stdout" ,cmdPath = nam "path" &= help "Directory in which to search for files" ,cmdCppDefine = nam_ "cpp-define" &= typ "NAME[=VALUE]" &= help "CPP #define" ,cmdCppInclude = nam_ "cpp-include" &= typDir &= help "CPP include path" ,cmdCppFile = nam_ "cpp-file" &= typFile &= help "CPP pre-include file" ,cmdCppSimple = nam_ "cpp-simple" &= help "Use a simple CPP (strip # lines)" ,cmdCppAnsi = nam_ "cpp-ansi" &= help "Use CPP in ANSI compatibility mode" ,cmdJson = nam_ "json" &= help "Display hint data as JSON" ,cmdCC = nam_ "cc" &= help "Display hint data as Code Climate Issues" ,cmdNoSummary = nam_ "no-summary" &= help "Do not show summary information" ,cmdOnly = nam "only" &= typ "HINT" &= help "Specify which hints explicitly" ,cmdNoExitCode = nam_ "no-exit-code" &= help "Do not give a negative exit if hints" ,cmdSerialise = nam_ "serialise" &= help "Serialise hint data for consumption by apply-refact" ,cmdRefactor = nam_ "refactor" &= help "Automatically invoke `refactor` to apply hints" ,cmdRefactorOptions = nam_ "refactor-options" &= typ "OPTIONS" &= help "Options to pass to the `refactor` executable" , cmdWithRefactor = nam_ "with-refactor" &= help "Give the path to refactor" } &= auto &= explicit &= name "lint" ,CmdGrep {cmdFiles = def &= args &= typ "FILE/DIR" ,cmdPattern = def &= argPos 0 &= typ "PATTERN" } &= explicit &= name "grep" ,CmdTest {cmdProof = nam_ "proof" &= typFile &= help "Isabelle/HOLCF theory file" ,cmdTypeCheck = nam_ "typecheck" &= help "Use GHC to type check the hints" ,cmdQuickCheck = nam_ "quickcheck" &= help "Use QuickCheck to check the hints" ,cmdTempDir = nam_ "tempdir" &= help "Where to put temporary files (not cleaned up)" } &= explicit &= name "test" &= details ["HLint gives hints on how to improve Haskell code." ,"" ,"To check all Haskell files in 'src' and generate a report type:" ," hlint src --report"] ,CmdHSE {} &= explicit &= name "hse" ] &= program "hlint" &= verbosity &= summary ("HLint v" ++ showVersion version ++ ", (C) Neil Mitchell 2006-2017") where nam xs = nam_ xs &= name [head xs] nam_ xs = def &= explicit &= name xs -- | Where should we find the configuration files? -- * If someone passes cmdWithHints, only look at files they explicitly request -- * If someone passes an explicit hint name, automatically merge in data/hlint.yaml -- We want more important hints to go last, since they override cmdHintFiles :: Cmd -> IO [FilePath] cmdHintFiles cmd = do let explicit1 = [cmdDataDir cmd "hlint.yaml" | null $ cmdWithHints cmd] let explicit2 = cmdGivenHints cmd bad <- filterM (notM . doesFileExist) $ explicit1 ++ explicit2 when (bad /= []) $ fail $ unlines $ "Failed to find requested hint files:" : map (" "++) bad if cmdWithHints cmd /= [] then return $ explicit1 ++ explicit2 else do -- we follow the stylish-haskell config file search policy -- 1) current directory or its ancestors; 2) home directory curdir <- getCurrentDirectory home <- getHomeDirectory b <- doesFileExist ".hlint.yaml" implicit <- findM doesFileExist $ "HLint.hs" : -- the default in HLint 1.* map ( ".hlint.yaml") (ancestors curdir ++ [home]) -- to match Stylish Haskell return $ explicit1 ++ maybeToList implicit ++ explicit2 where ancestors = init . map joinPath . reverse . inits . splitPath cmdExtensions :: Cmd -> (Language, [Extension]) cmdExtensions = getExtensions . cmdLanguage cmdCpp :: Cmd -> CppFlags cmdCpp cmd | cmdCppSimple cmd = CppSimple | EnableExtension CPP `elem` snd (cmdExtensions cmd) = Cpphs defaultCpphsOptions {boolopts=defaultBoolOptions{hashline=False, stripC89=True, ansi=cmdCppAnsi cmd} ,includes = cmdCppInclude cmd ,preInclude = cmdCppFile cmd ,defines = [(a,drop 1 b) | x <- cmdCppDefine cmd, let (a,b) = break (== '=') x] } | otherwise = NoCpp -- | Determines whether to use colour or not. cmdUseColour :: Cmd -> IO Bool cmdUseColour cmd = case cmdColor cmd of Always -> return True Never -> return False Auto -> hSupportsANSI stdout "." <\> x = x x <\> y = x y resolveFile :: Cmd -> Maybe FilePath -- ^ Temporary file -> FilePath -- ^ File to resolve, may be "-" for stdin -> IO [FilePath] resolveFile cmd = getFile (cmdPath cmd) (cmdExtension cmd) getFile :: [FilePath] -> [String] -> Maybe FilePath -> FilePath -> IO [FilePath] getFile path _ (Just tmpfile) "-" = getContents >>= writeFile tmpfile >> return [tmpfile] getFile path _ Nothing "-" = return ["-"] getFile [] exts _ file = exitMessage $ "Couldn't find file: " ++ file getFile (p:ath) exts t file = do isDir <- doesDirectoryExist $ p <\> file if isDir then do let avoidDir x = let y = takeFileName x in "_" `isPrefixOf` y || ("." `isPrefixOf` y && not (all (== '.') y)) avoidFile x = let y = takeFileName x in "." `isPrefixOf` y xs <- listFilesInside (return . not . avoidDir) $ p <\> file return [x | x <- xs, drop 1 (takeExtension x) `elem` exts, not $ avoidFile x] else do isFil <- doesFileExist $ p <\> file if isFil then return [p <\> file] else do res <- getModule p exts file case res of Just x -> return [x] Nothing -> getFile ath exts t file getModule :: FilePath -> [String] -> FilePath -> IO (Maybe FilePath) getModule path exts x | not (any isSpace x) && all isMod xs = f exts where xs = words $ map (\x -> if x == '.' then ' ' else x) x isMod (x:xs) = isUpper x && all (\x -> isAlphaNum x || x == '_') xs isMod _ = False pre = path <\> joinPath xs f [] = return Nothing f (x:xs) = do let s = pre <.> x b <- doesFileExist s if b then return $ Just s else f xs getModule _ _ _ = return Nothing getExtensions :: [String] -> (Language, [Extension]) getExtensions args = (lang, foldl f (if null langs then defaultExtensions else []) exts) where lang = if null langs then baseLanguage defaultParseMode else fromJust $ lookup (last langs) ls (langs, exts) = partition (isJust . flip lookup ls) args ls = [(show x, x) | x <- knownLanguages] f a "Haskell98" = [] f a ('N':'o':x) | Just x <- readExtension x = delete x a f a x | Just x <- readExtension x = x : delete x a f a x = exitMessageImpure $ "Unknown extension: " ++ x readExtension :: String -> Maybe Extension readExtension x = case classifyExtension x of UnknownExtension _ -> Nothing x -> Just x hlint-2.0.11/src/CC.hs0000644000000000000000000000752213210071537012514 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -- | -- -- Utility for formatting @'Idea'@ data in accordance with the Code Climate -- spec: -- module CC ( printIssue , fromIdea ) where import Data.Aeson (ToJSON(..), (.=), encode, object) import Data.Char (toUpper) import Data.Monoid ((<>)) import Data.Text (Text) import Language.Haskell.Exts.SrcLoc (SrcSpan(..)) import qualified Data.Text as T import qualified Data.ByteString.Lazy.Char8 as C8 import Idea (Idea(..), Severity(..)) data Issue = Issue { issueType :: Text , issueCheckName :: Text , issueDescription :: Text , issueContent :: Text , issueCategories :: [Text] , issueLocation :: Location , issueRemediationPoints :: Int } data Location = Location FilePath Position Position data Position = Position Int Int instance ToJSON Issue where toJSON Issue{..} = object [ "type" .= issueType , "check_name" .= issueCheckName , "description" .= issueDescription , "content" .= object [ "body" .= issueContent ] , "categories" .= issueCategories , "location" .= issueLocation , "remediation_points" .= issueRemediationPoints ] instance ToJSON Location where toJSON (Location path begin end) = object [ "path" .= path , "positions" .= object [ "begin" .= begin , "end" .= end ] ] instance ToJSON Position where toJSON (Position line column) = object [ "line" .= line , "column" .= column ] -- | Print an @'Issue'@ with trailing null-terminator and newline -- -- The trailing newline will be ignored, but makes the output more readable -- printIssue :: Issue -> IO () printIssue = C8.putStrLn . (<> "\0") . encode -- | Convert an hlint @'Idea'@ to a datatype more easily serialized for CC fromIdea :: Idea -> Issue fromIdea Idea{..} = Issue { issueType = "issue" , issueCheckName = "HLint/" <> T.pack (camelize ideaHint) , issueDescription = T.pack ideaHint , issueContent = content ideaFrom ideaTo <> listNotes ideaNote , issueCategories = categories ideaHint , issueLocation = fromSrcSpan ideaSpan , issueRemediationPoints = points ideaSeverity } where content from Nothing = T.unlines [ "Found" , "" , "```" , T.pack from , "```" , "" , "remove it." ] content from (Just to) = T.unlines [ "Found" , "" , "```" , T.pack from , "```" , "" , "Why not" , "" , "```" , T.pack to , "```" ] listNotes [] = "" listNotes notes = T.unlines $ [ "" , "Applying this change:" , "" ] ++ map (("* " <>) . T.pack . show) notes categories _ = ["Style"] points Ignore = 0 points Suggestion = basePoints points Warning = 5 * basePoints points Error = 10 * basePoints fromSrcSpan :: SrcSpan -> Location fromSrcSpan SrcSpan{..} = Location (locationFileName srcSpanFilename) (Position srcSpanStartLine srcSpanStartColumn) (Position srcSpanEndLine srcSpanEndColumn) where locationFileName ('.':'/':x) = x locationFileName x = x camelize :: String -> String camelize = concatMap capitalize . words capitalize :: String -> String capitalize [] = [] capitalize (c:rest) = toUpper c : rest -- "The baseline remediation points value is 50,000, which is the time it takes -- to fix a trivial code style issue like a missing semicolon on a single line, -- including the time for the developer to open the code, make the change, and -- confidently commit the fix. All other remediation points values are expressed -- in multiples of that Basic Remediation Point Value." basePoints :: Int basePoints = 50000 hlint-2.0.11/src/Apply.hs0000644000000000000000000001035013210071537013305 0ustar0000000000000000 module Apply(applyHints, applyHintFile, applyHintFiles) where import Control.Applicative import Data.Monoid import HSE.All import Hint.All import Idea import Data.Tuple.Extra import Data.Either import Data.List.Extra import Data.Maybe import Data.Ord import Config.Type import Config.Haskell import Prelude -- | Apply hints to a single file, you may have the contents of the file. applyHintFile :: ParseFlags -> [Setting] -> FilePath -> Maybe String -> IO [Idea] applyHintFile flags s file src = do res <- parseModuleApply flags s file src return $ case res of Left err -> [err] Right m -> executeHints s [m] -- | Apply hints to multiple files, allowing cross-file hints to fire. applyHintFiles :: ParseFlags -> [Setting] -> [FilePath] -> IO [Idea] applyHintFiles flags s files = do (err, ms) <- partitionEithers <$> mapM (\file -> parseModuleApply flags s file Nothing) files return $ err ++ executeHints s ms -- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's. -- The 'Idea' values will be ordered within a file. -- -- Given a set of modules, it may be faster pass each to 'applyHints' in a singleton list. -- When given multiple modules at once this function attempts to find hints between modules, -- which is slower and often pointless (by default HLint passes modules singularly, using -- @--cross@ to pass all modules together). applyHints {- PUBLIC -} :: [Classify] -> Hint -> [(Module SrcSpanInfo, [Comment])] -> [Idea] applyHints cs = applyHintsReal $ map SettingClassify cs applyHintsReal :: [Setting] -> Hint -> [(Module_, [Comment])] -> [Idea] applyHintsReal settings hints_ ms = concat $ [ map (classify $ cls ++ mapMaybe readPragma (universeBi m)) $ order "" (hintModule hints settings nm m) `merge` concat [order (fromNamed d) $ decHints d | d <- moduleDecls m] `merge` concat [order "" $ hintComment hints settings c | c <- cs] | (nm,(m,cs)) <- mns , let decHints = hintDecl hints settings nm m -- partially apply , let order n = map (\i -> i{ideaModule=moduleName m, ideaDecl=n}) . sortBy (comparing ideaSpan) , let merge = mergeBy (comparing ideaSpan)] ++ [map (classify cls) (hintModules hints settings $ map (second fst) mns)] where cls = [x | SettingClassify x <- settings] mns = map (scopeCreate . fst &&& id) ms hints = (if length ms <= 1 then noModules else id) hints_ noModules h = h{hintModules = \_ _ -> []} `mappend` mempty{hintModule = \s a b -> hintModules h s [(a,b)]} -- | Given a list of settings (a way to classify) and a list of hints, run them over a list of modules. executeHints :: [Setting] -> [(Module_, [Comment])] -> [Idea] executeHints s = applyHintsReal s (allHints s) -- | Return either an idea (a parse error) or the module. In IO because might call the C pre processor. parseModuleApply :: ParseFlags -> [Setting] -> FilePath -> Maybe String -> IO (Either Idea (Module_, [Comment])) parseModuleApply flags s file src = do res <- parseModuleEx (parseFlagsAddFixities [x | Infix x <- s] flags) file src case res of Right m -> return $ Right m Left (ParseError sl msg ctxt) -> do i <- return $ rawIdeaN Error "Parse error" (mkSrcSpan sl sl) ctxt Nothing [] i <- return $ classify [x | SettingClassify x <- s] i return $ Left i -- | Find which hints a list of settings implies. allHints :: [Setting] -> Hint allHints xs = mconcat $ hintRules [x | SettingMatchExp x <- xs] : map f builtin where builtin = nub $ concat [if x == "All" then map fst builtinHints else [x] | Builtin x <- xs] f x = fromMaybe (error $ "Unknown builtin hints: HLint.Builtin." ++ x) $ lookup x builtinHints -- | Given some settings, make sure the severity field of the Idea is correct. classify :: [Classify] -> Idea -> Idea classify xs i = let s = foldl' (f i) (ideaSeverity i) xs in s `seq` i{ideaSeverity=s} where -- figure out if we need to change the severity f :: Idea -> Severity -> Classify -> Severity f i r c | classifyHint c ~= ideaHint i && classifyModule c ~= ideaModule i && classifyDecl c ~= ideaDecl i = classifySeverity c | otherwise = r x ~= y = null x || x == y hlint-2.0.11/src/Test/0000755000000000000000000000000013210071537012604 5ustar0000000000000000hlint-2.0.11/src/Test/Util.hs0000644000000000000000000000223113210071537014053 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} module Test.Util( withTests, tested, passed, failed, progress ) where import Data.IORef import System.IO.Unsafe import Control.Monad data Result = Result {failures :: Int, total :: Int} deriving Show {-# NOINLINE ref #-} ref :: IORef [Result] ref = unsafePerformIO $ newIORef [] -- | Returns the number of failing tests. -- Warning: Not multithread safe, but is reenterant withTests :: IO () -> IO Int withTests act = do atomicModifyIORef ref $ \r -> (Result 0 0 : r, ()) act Result{..} <- atomicModifyIORef ref $ \(r:rs) -> (rs, r) putStrLn "" putStrLn $ if failures == 0 then "Tests passed (" ++ show total ++ ")" else "Tests failed (" ++ show failures ++ " of " ++ show total ++ ")" return failures progress :: IO () progress = putChar '.' passed :: IO () passed = atomicModifyIORef ref $ \(r:rs) -> (r{total=total r+1}:rs, ()) failed :: [String] -> IO () failed xs = do unless (null xs) $ putStrLn $ unlines $ "" : xs atomicModifyIORef ref $ \(r:rs) -> (r{total=total r+1, failures=failures r+1}:rs, ()) tested :: Bool -> IO () tested b = if b then passed else failed [] hlint-2.0.11/src/Test/Translate.hs0000644000000000000000000001104613210071537015077 0ustar0000000000000000 -- | Translate the hints to Haskell and run with GHC. module Test.Translate(testTypeCheck, testQuickCheck) where import Control.Monad import Data.List.Extra import System.IO.Extra import Data.Maybe import System.Process import System.Exit import System.FilePath import Config.Type import HSE.All import Test.Util runMains :: FilePath -> FilePath -> [String] -> IO () runMains datadir tmpdir xs = (if tmpdir == "" then withTempDir else ($ tmpdir)) $ \dir -> do ms <- forM (zip [1..] xs) $ \(i,x) -> do let m = "I" ++ show i writeFile (dir m <.> "hs") $ replace "module Main" ("module " ++ m) x return m writeFile (dir "Main.hs") $ unlines $ ["import qualified " ++ m | m <- ms] ++ ["main = do"] ++ [" " ++ m ++ ".main" | m <- ms] res <- system $ "runhaskell -i" ++ dir ++ " -i" ++ datadir ++ " Main" replicateM_ (length xs) $ tested $ res == ExitSuccess -- | Given a set of hints, do all the HintRule hints type check testTypeCheck :: FilePath -> FilePath -> [[Setting]] -> IO () testTypeCheck = wrap toTypeCheck -- | Given a set of hints, do all the HintRule hints satisfy QuickCheck testQuickCheck :: FilePath -> FilePath -> [[Setting]] -> IO () testQuickCheck = wrap toQuickCheck wrap :: ([HintRule] -> [String]) -> FilePath -> FilePath -> [[Setting]] -> IO () wrap f datadir tmpdir hints = runMains datadir tmpdir [unlines $ body [x | SettingMatchExp x <- xs] | xs <- hints] where body xs = ["{-# LANGUAGE NoMonomorphismRestriction, ExtendedDefaultRules, ScopedTypeVariables, DeriveDataTypeable #-}" ,"{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}" ,"module Main(main) where"] ++ concat [map (prettyPrint . hackImport) $ scopeImports $ hintRuleScope x | x <- take 1 xs] ++ f xs -- Hack around haskell98 not being compatible with base anymore hackImport i@ImportDecl{importAs=Just a,importModule=b} | prettyPrint b `elem` words "Maybe List Monad IO Char" = i{importAs=Just b,importModule=a} hackImport i = i --------------------------------------------------------------------- -- TYPE CHECKING toTypeCheck :: [HintRule] -> [String] toTypeCheck hints = ["import HLint_TypeCheck hiding(main)" ,"main = return ()"] ++ ["{-# LINE " ++ show (startLine $ ann rhs) ++ " " ++ show (fileName $ ann rhs) ++ " #-}\n" ++ prettyPrint (PatBind an (toNamed $ "test" ++ show i) bod Nothing) | (i, HintRule _ _ _ lhs rhs side _) <- zip [1..] hints, "noTypeCheck" `notElem` vars (maybeToList side) , let vs = map toNamed $ nub $ filter isUnifyVar $ vars lhs ++ vars rhs , let inner = InfixApp an (Paren an lhs) (toNamed "==>") (Paren an rhs) , let bod = UnGuardedRhs an $ if null vs then inner else Lambda an vs inner] --------------------------------------------------------------------- -- QUICKCHECK toQuickCheck :: [HintRule] -> [String] toQuickCheck hints = ["import HLint_QuickCheck hiding(main)" ,"default(Maybe Bool,Int,Dbl)" ,prettyPrint $ PatBind an (toNamed "main") (UnGuardedRhs an $ toNamed "withMain" $$ Do an tests) Nothing] where str x = Lit an $ String an x (show x) int x = Lit an $ Int an (toInteger x) (show x) app = App an a $$ b = InfixApp an a (toNamed "$") b tests = [ Qualifier an $ Let an (BDecls an [PatBind an (toNamed "t") (UnGuardedRhs an bod) Nothing]) $ (toNamed "test" `app` str (fileName $ ann rhs) `app` int (startLine $ ann rhs) `app` str (prettyPrint lhs ++ " ==> " ++ prettyPrint rhs)) `app` toNamed "t" | (i, HintRule _ _ _ lhs rhs side note) <- zip [1..] hints, "noQuickCheck" `notElem` vars (maybeToList side) , let vs = map (restrict side) $ nub $ filter isUnifyVar $ vars lhs ++ vars rhs , let op = if any isRemovesError note then "?==>" else "==>" , let inner = InfixApp an (Paren an lhs) (toNamed op) (Paren an rhs) , let bod = if null vs then Paren an inner else Lambda an vs inner] restrict (Just side) v | any (=~= App an (toNamed "isNegZero") (toNamed v)) (universe side) = PApp an (toNamed "NegZero") [toNamed v] | any (=~= App an (toNamed "isNat") (toNamed v)) (universe side) = PApp an (toNamed "Nat") [toNamed v] | any (=~= App an (toNamed "isCompare") (toNamed v)) (universe side) = PApp an (toNamed "Compare") [toNamed v] restrict _ v = toNamed v isRemovesError RemovesError{} = True isRemovesError _ = False hlint-2.0.11/src/Test/Proof.hs0000644000000000000000000002123113210071537014224 0ustar0000000000000000{-# LANGUAGE RecordWildCards, PatternGuards, FlexibleContexts #-} -- | Check the coverage of the hints given a list of Isabelle theorems module Test.Proof(proof) where import Data.Tuple.Extra import Control.Applicative import Control.Monad import Control.Monad.Trans.State import Data.Char import Data.List import Data.Maybe import Data.Function import System.FilePath import Config.Type import HSE.All import Prelude data Theorem = Theorem {original :: Maybe HintRule ,location :: String ,lemma :: String } instance Eq Theorem where t1 == t2 = lemma t1 == lemma t2 instance Show Theorem where show Theorem{..} = location ++ ":\n" ++ maybe "" f original ++ lemma ++ "\n" where f HintRule{..} = "(* " ++ prettyPrint hintRuleLHS ++ " ==> " ++ prettyPrint hintRuleRHS ++ " *)\n" proof :: [FilePath] -> [Setting] -> FilePath -> IO () proof reports hints thy = do got <- isabelleTheorems (takeFileName thy) <$> readFile thy let want = nub $ hintTheorems hints let unused = got \\ want let missing = want \\ got let reasons = map (\x -> (fst $ head x, map snd x)) $ groupBy ((==) `on` fst) $ sortBy (compare `on` fst) $ map (classifyMissing &&& id) missing let summary = table $ let (*) = (,) in ["HLint hints" * want ,"HOL proofs" * got ,"Useful proofs" * (got `intersect` want) ,"Unused proofs" * unused ,"Unproved hints" * missing] ++ [(" " ++ name) * ps | (name,ps) <- reasons] putStr $ unlines summary forM_ reports $ \report -> do let out = ("Unused proofs",unused) : map (first ("Unproved hints - " ++)) reasons writeFile report $ unlines $ summary ++ "" : concat [("== " ++ a ++ " ==") : "" : map show b | (a,b) <- out] putStrLn $ "Report written to " ++ report where table xs = [a ++ replicate (n + 6 - length a - length bb) ' ' ++ bb | (a,b) <- xs, let bb = show $ length b] where n = maximum $ map (length . fst) xs missingFuncs = let a*b = [(b,a) | b <- words b] in concat ["IO" * "putChar putStr print putStrLn getLine getChar getContents hReady hPrint stdin" ,"Exit" * "exitSuccess" ,"Ord" * "(>) (<=) (>=) (<) compare minimum maximum sort sortBy" ,"Show" * "show shows showIntAtBase" ,"Read" * "reads read" ,"String" * "lines unlines words unwords" ,"Monad" * "mapM mapM_ sequence sequence_ msum mplus mzero liftM when unless return evaluate join void (>>=) (<=<) (>=>) forever ap" ,"Functor" * "fmap" ,"Numeric" * "(+) (*) fromInteger fromIntegral negate log (/) (-) (*) (^^) (^) subtract sqrt even odd" ,"Char" * "isControl isPrint isUpper isLower isAlpha isDigit" ,"Arrow" * "second first (***) (&&&)" ,"Applicative+" * "traverse for traverse_ for_ pure (<|>) (<**>)" ,"Exception" * "catch handle catchJust bracket error toException" ,"WeakPtr" * "mkWeak" ] -- | Guess why a theorem is missing classifyMissing :: Theorem -> String classifyMissing Theorem{original = Just HintRule{..}} | _:_ <- [v :: Exp_ | v@Case{} <- universeBi (hintRuleLHS,hintRuleRHS)] = "case" | _:_ <- [v :: Exp_ | v@ListComp{} <- universeBi (hintRuleLHS,hintRuleRHS)] = "list-comp" | v:_ <- mapMaybe (`lookup` missingFuncs) [prettyPrint (v :: Name S) | v <- universeBi (hintRuleLHS,hintRuleRHS)] = v classifyMissing _ = "?unknown" -- Extract theorems out of Isabelle code (HLint.thy) isabelleTheorems :: FilePath -> String -> [Theorem] isabelleTheorems file = find . lexer 1 where find ((i,"lemma"):(_,'\"':lemma):rest) = Theorem Nothing (file ++ ":" ++ show i) lemma : find rest find ((i,"lemma"):(_,name):(_,":"):(_,'\"':lemma):rest) = Theorem Nothing (file ++ ":" ++ show i) lemma : find rest find ((i,"lemma"):(_,"assumes"):(_,'\"':assumes):(_,"shows"):(_,'\"':lemma):rest) = Theorem Nothing (file ++ ":" ++ show i) (assumes ++ " \\ " ++ lemma) : find rest find ((i,"lemma"):rest) = Theorem Nothing (file ++ ":" ++ show i) "Unsupported lemma format" : find rest find (x:xs) = find xs find [] = [] lexer i x | i `seq` False = [] | Just x <- stripPrefix "(*" x, (a,b) <- breaks "*)" x = lexer (add a i) b | Just x <- stripPrefix "\"" x, (a,b) <- breaks "\"" x = (i,'\"':a) : lexer (add a i) b -- NOTE: drop the final " | x:xs <- x, isSpace x = lexer (add [x] i) xs | (a@(_:_),b) <- span (\y -> y == '_' || isAlpha y) x = (i,a) : lexer (add a i) b lexer i (x:xs) = (i,[x]) : lexer (add [x] i) xs lexer i [] = [] add s i = length (filter (== '\n') s) + i breaks s x | Just x <- stripPrefix s x = ("",x) breaks s (x:xs) = let (a,b) = breaks s xs in (x:a,b) breaks s [] = ([],[]) reparen :: Setting -> Setting reparen (SettingMatchExp m@HintRule{..}) = SettingMatchExp m{hintRuleLHS = f False hintRuleLHS, hintRuleRHS = f True hintRuleRHS} where f right x = if isLambda x || isIf x || badInfix x then Paren (ann x) x else x badInfix (InfixApp _ _ op _) = prettyPrint op `elem` words "|| && ." badInfix _ = False reparen x = x -- Extract theorems out of the hints hintTheorems :: [Setting] -> [Theorem] hintTheorems xs = [ Theorem (Just m) (loc $ ann hintRuleLHS) $ maybe "" assumes hintRuleSide ++ relationship hintRuleNotes a b | SettingMatchExp m@HintRule{..} <- map reparen xs, let a = exp1 $ typeclasses hintRuleNotes hintRuleLHS, let b = exp1 hintRuleRHS, a /= b] where loc (SrcSpanInfo (SrcSpan file ln _ _ _) _) = takeFileName file ++ ":" ++ show ln subs xs = flip lookup [(reverse b, reverse a) | x <- words xs, let (a,'=':b) = break (== '=') $ reverse x] funs = subs "id=ID not=neg or=the_or and=the_and (||)=tror (&&)=trand (++)=append (==)=eq (/=)=neq ($)=dollar" ops = subs "||=orelse &&=andalso .=oo ===eq /==neq ++=++ !!=!! $=dollar $!=dollarBang" pre = flip elem $ words "eq neq dollar dollarBang" cons = subs "True=TT False=FF" typeclasses hintRuleNotes x = foldr f x hintRuleNotes where f (ValidInstance cls var) x = evalState (transformM g x) True where g v@Var{} | v ~= var = do b <- get; put False return $ if b then Paren an $ toNamed $ prettyPrint v ++ "::'a::" ++ cls ++ "_sym" else v g v = return v :: State Bool Exp_ f _ x = x relationship hintRuleNotes a b | any lazier hintRuleNotes = a ++ " \\ " ++ b | DecreasesLaziness `elem` hintRuleNotes = b ++ " \\ " ++ a | otherwise = a ++ " = " ++ b where lazier IncreasesLaziness = True lazier RemovesError{} = True lazier _ = False assumes (App _ op var) | op ~= "isNat" = "le\\0\\" ++ prettyPrint var ++ " \\ FF \\ " | op ~= "isNegZero" = "gt\\0\\" ++ prettyPrint var ++ " \\ FF \\ " assumes (App _ op var) | op ~= "isWHNF" = prettyPrint var ++ " \\ \\ \\ " assumes _ = "" exp1 = exp . transformBi unqual -- Syntax translations exp (App _ a b) = exp a ++ "\\" ++ exp b exp (Paren _ x) = "(" ++ exp x ++ ")" exp (Var _ x) | Just x <- funs $ prettyPrint x = x exp (Con _ (Special _ (TupleCon _ _ i))) = "\\" ++ replicate (i-1) ',' ++ "\\" exp (Con _ x) | Just x <- cons $ prettyPrint x = x exp (Tuple _ _ xs) = "\\" ++ intercalate ", " (map exp xs) ++ "\\" exp (If _ a b c) = "If " ++ exp a ++ " then " ++ exp b ++ " else " ++ exp c exp (Lambda _ xs y) = "\\ " ++ unwords (map pat xs) ++ ". " ++ exp y exp (InfixApp _ x op y) | Just op <- ops $ prettyPrint op = if pre op then op ++ "\\" ++ exp (paren x) ++ "\\" ++ exp (paren y) else exp x ++ " " ++ op ++ " " ++ exp y -- Translations from the Haskell 2010 report exp (InfixApp l a (QVarOp _ b) c) = exp $ App l (App l (Var l b) a) c -- S3.4 exp x@(LeftSection l e op) = let v = fresh x in exp $ Paren l $ Lambda l [toNamed v] $ InfixApp l e op (toNamed v) -- S3.5 exp x@(RightSection l op e) = let v = fresh x in exp $ Paren l $ Lambda l [toNamed v] $ InfixApp l (toNamed v) op e -- S3.5 exp x = prettyPrint x pat (PTuple _ _ xs) = "\\" ++ intercalate ", " (map pat xs) ++ "\\" pat x = prettyPrint x fresh x = head $ ("z":["v" ++ show i | i <- [1..]]) \\ vars x hlint-2.0.11/src/Test/InputOutput.hs0000644000000000000000000000772313210071537015471 0ustar0000000000000000{-# LANGUAGE PatternGuards, ScopedTypeVariables, RecordWildCards, ViewPatterns #-} -- | Check the input/output pairs in the tests/ directory module Test.InputOutput(testInputOutput) where import Control.Applicative import Data.Tuple.Extra import Control.Exception import Control.Monad import Data.List.Extra import Data.IORef import System.Directory import System.FilePath import System.Console.CmdArgs.Explicit import System.Console.CmdArgs.Verbosity import System.Exit import System.IO.Extra import Prelude import Test.Util testInputOutput :: ([String] -> IO ()) -> IO () testInputOutput main = do xs <- getDirectoryContents "tests" xs <- return $ filter ((==) ".test" . takeExtension) xs forM_ xs $ \file -> do ios <- parseInputOutputs <$> readFile ("tests" file) forM_ (zip [1..] ios) $ \(i,io@InputOutput{..}) -> do progress forM_ files $ \(name,contents) -> do createDirectoryIfMissing True $ takeDirectory name writeFile name contents checkInputOutput main io{name= "_" ++ takeBaseName file ++ "_" ++ show i} mapM_ (removeFile . fst) $ concatMap files ios data InputOutput = InputOutput {name :: String ,files :: [(FilePath, String)] ,run :: [String] ,output :: String ,exit :: Maybe ExitCode } deriving Eq parseInputOutputs :: String -> [InputOutput] parseInputOutputs = f z . lines where z = InputOutput "unknown" [] [] "" Nothing interest x = any (`isPrefixOf` x) ["----","FILE","RUN","OUTPUT","EXIT"] f io ((stripPrefix "RUN " -> Just flags):xs) = f io{run = splitArgs flags} xs f io ((stripPrefix "EXIT " -> Just code):xs) = f io{exit = Just $ let i = read code in if i == 0 then ExitSuccess else ExitFailure i} xs f io ((stripPrefix "FILE " -> Just file):xs) | (str,xs) <- g xs = f io{files = files io ++ [(file,unlines str)]} xs f io ("OUTPUT":xs) | (str,xs) <- g xs = f io{output = unlines str} xs f io ((isPrefixOf "----" -> True):xs) = [io | io /= z] ++ f z xs f io [] = [io | io /= z] f io (x:xs) = error $ "Unknown test item, " ++ x g = first (reverse . dropWhile null . reverse) . break interest --------------------------------------------------------------------- -- CHECK INPUT/OUTPUT PAIRS checkInputOutput :: ([String] -> IO ()) -> InputOutput -> IO () checkInputOutput main InputOutput{..} = do code <- newIORef ExitSuccess got <- fmap (reverse . dropWhile null . reverse . map trimEnd . lines . fst) $ captureOutput $ handle (\(e::SomeException) -> print e) $ handle (\(e::ExitCode) -> writeIORef code e) $ bracket getVerbosity setVerbosity $ const $ setVerbosity Normal >> main run code <- readIORef code (want,got) <- return $ matchStarStar (lines output) got if maybe False (/= code) exit then failed ["TEST FAILURE IN tests/" ++ name ,"WRONG EXIT CODE" ,"GOT : " ++ show code ,"WANT: " ++ show exit ] else if length got == length want && and (zipWith matchStar want got) then passed else do let trail = replicate (max (length got) (length want)) "" let (i,g,w):_ = [(i,g,w) | (i,g,w) <- zip3 [1..] (got++trail) (want++trail), not $ matchStar w g] failed $ ["TEST FAILURE IN tests/" ++ name ,"DIFFER ON LINE: " ++ show i ,"GOT : " ++ g ,"WANT: " ++ w ,"FULL OUTPUT FOR GOT:"] ++ got -- | First string may have stars in it (the want) matchStar :: String -> String -> Bool matchStar ('*':xs) ys = any (matchStar xs) $ tails ys matchStar (x:xs) (y:ys) = x == y && matchStar xs ys matchStar [] [] = True matchStar _ _ = False matchStarStar :: [String] -> [String] -> ([String], [String]) matchStarStar want got = case break (== "**") want of (_, []) -> (want, got) (w1,_:w2) -> (w1++w2, g1 ++ takeEnd (length w2) g2) where (g1,g2) = splitAt (length w1) got hlint-2.0.11/src/Test/Annotations.hs0000644000000000000000000000613013210071537015435 0ustar0000000000000000{-# LANGUAGE PatternGuards, RecordWildCards #-} -- | Check the annotations within source and hint files. module Test.Annotations(testAnnotations) where import Data.Tuple.Extra import Data.Char import Data.List.Extra import Data.Maybe import Data.Function import Config.Type import Idea import Apply import HSE.All import Test.Util -- Input, Output -- Output = Nothing, should not match -- Output = Just xs, should match xs data Test = Test SrcLoc String (Maybe String) testAnnotations :: [Setting] -> FilePath -> IO () testAnnotations setting file = do tests <- parseTestFile file mapM_ f tests where f (Test loc inp out) = do ideas <- applyHintFile defaultParseFlags setting file $ Just inp let good = case out of Nothing -> null ideas Just x -> length ideas == 1 && seq (length (show ideas)) True && -- force, mainly for hpc match x (head ideas) let bad = [failed $ ["TEST FAILURE (" ++ show (length ideas) ++ " hints generated)" ,"SRC: " ++ showSrcLoc loc ,"INPUT: " ++ inp] ++ map ((++) "OUTPUT: " . show) ideas ++ ["WANTED: " ++ fromMaybe "" out] | not good] ++ [failed ["TEST FAILURE (BAD LOCATION)" ,"SRC: " ++ showSrcLoc loc ,"INPUT: " ++ inp ,"OUTPUT: " ++ show i] | i@Idea{..} <- ideas, let SrcLoc{..} = getPointLoc ideaSpan, srcFilename == "" || srcLine == 0 || srcColumn == 0] if null bad then passed else sequence_ bad match "???" _ = True match x y | "@" `isPrefixOf` x = a == show (ideaSeverity y) && match (trimStart b) y where (a,b) = break isSpace $ tail x match x y = on (==) norm (fromMaybe "" $ ideaTo y) x -- FIXME: Should use a better check for expected results norm = filter $ \x -> not (isSpace x) && x /= ';' parseTestFile :: FilePath -> IO [Test] parseTestFile file = do src <- readFile file -- we remove all leading # symbols since Yaml only lets us do comments that way return $ f False $ zip [1..] $ map (\x -> fromMaybe x $ stripPrefix "# " x) $ lines src where open = isPrefixOf "" shut = isPrefixOf "" f False ((i,x):xs) = f (open x) xs f True ((i,x):xs) | shut x = f False xs | null x || "-- " `isPrefixOf` x = f True xs | "\\" `isSuffixOf` x, (_,y):ys <- xs = f True $ (i,init x++"\n"++y):ys | otherwise = parseTest file i x : f True xs f _ [] = [] parseTest file i x = uncurry (Test (SrcLoc file i 0)) $ f x where f x | Just x <- stripPrefix "" x = first ("--"++) $ f x f (' ':'-':'-':xs) | null xs || " " `isPrefixOf` xs = ("", Just $ dropWhile isSpace xs) f (x:xs) = first (x:) $ f xs f [] = ([], Nothing) hlint-2.0.11/src/Test/All.hs0000644000000000000000000000561113210071537013653 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} module Test.All(test) where import Control.Exception import Control.Monad import Data.Char import Data.List import System.Directory import System.FilePath import Data.Functor import Prelude import Config.Type import Config.Read import CmdLine import HSE.All import Hint.All import Test.Util import Test.InputOutput import Test.Annotations import Test.Translate import System.IO.Extra test :: Cmd -> ([String] -> IO ()) -> FilePath -> [FilePath] -> IO Int test CmdTest{..} main dataDir files = withBuffering stdout NoBuffering $ withTests $ do hasSrc <- doesFileExist "hlint.cabal" useSrc <- return $ hasSrc && null files testFiles <- if files /= [] then return files else do xs <- getDirectoryContents dataDir return [dataDir x | x <- xs, takeExtension x `elem` [".hs",".yml",".yaml"] , not $ "HLint_" `isPrefixOf` takeBaseName x] testFiles <- forM testFiles $ \file -> do hints <- readFilesConfig [(file, Nothing)] return (file, hints ++ (if takeBaseName file /= "Test" then [] else map (Builtin . fst) builtinHints)) let wrap msg act = putStr (msg ++ " ") >> act >> putStrLn "" putStrLn "Testing" checkCommentedYaml $ dataDir "default.yaml" config <- readFilesConfig [(".hlint.yaml",Nothing)] when useSrc $ wrap "Source annotations" $ forM_ builtinHints $ \(name,_) -> do progress testAnnotations (Builtin name : if name == "Restrict" then config else []) $ "src/Hint" name <.> "hs" when useSrc $ wrap "Input/outputs" $ testInputOutput main wrap "Hint names" $ mapM_ (\x -> do progress; testNames $ snd x) testFiles wrap "Hint annotations" $ forM_ testFiles $ \(file,h) -> do progress; testAnnotations h file when cmdTypeCheck $ wrap "Hint typechecking" $ progress >> testTypeCheck cmdDataDir cmdTempDir [h | (file, h) <- testFiles, takeFileName file /= "Test.hs"] when cmdQuickCheck $ wrap "Hint QuickChecking" $ progress >> testQuickCheck cmdDataDir cmdTempDir [h | (file, h) <- testFiles, takeFileName file /= "Test.hs"] when (null files && not hasSrc) $ putStrLn "Warning, couldn't find source code, so non-hint tests skipped" --------------------------------------------------------------------- -- VARIOUS SMALL TESTS testNames :: [Setting] -> IO () testNames hints = sequence_ [ failed ["No name for the hint " ++ prettyPrint hintRuleLHS ++ " ==> " ++ prettyPrint hintRuleRHS] | SettingMatchExp x@HintRule{..} <- hints, hintRuleName == defaultHintName] checkCommentedYaml :: FilePath -> IO () checkCommentedYaml file = do src <- lines <$> readFile' file let src2 = [x | x <- src, Just x <- [stripPrefix "# " x], not $ all (\x -> isAlpha x || x == '$') $ take 1 x] e <- readFilesConfig [(file, Just $ unlines src2)] void $ evaluate $ length e hlint-2.0.11/src/Language/0000755000000000000000000000000013210071537013410 5ustar0000000000000000hlint-2.0.11/src/Language/Haskell/0000755000000000000000000000000013210071537014773 5ustar0000000000000000hlint-2.0.11/src/Language/Haskell/HLint3.hs0000644000000000000000000001204613210071537016433 0ustar0000000000000000{-# LANGUAGE PatternGuards, RecordWildCards #-} -- | /WARNING: This module represents the evolving second version of the HLint API./ -- /It will be renamed to drop the "3" in the next major version./ -- -- This module provides a way to apply HLint hints. If you want to just run @hlint@ in-process -- and collect the results see 'hlint'. If you want to approximate the @hlint@ experience with -- a more structured API try: -- -- @ -- (flags, classify, hint) <- 'autoSettings' -- Right m <- 'parseModuleEx' flags \"MyFile.hs\" Nothing -- print $ 'applyHints' classify hint [m] -- @ module Language.Haskell.HLint3( hlint, applyHints, -- * Idea data type Idea(..), Severity(..), Note(..), -- * Settings Classify(..), getHLintDataDir, autoSettings, argsSettings, findSettings, readSettingsFile, -- * Hints HintBuiltin(..), HintRule(..), Hint(..), resolveHints, -- * Scopes Scope, scopeCreate, scopeMatch, scopeMove, -- * Haskell-src-exts parseModuleEx, defaultParseFlags, parseFlagsAddFixities, ParseError(..), ParseFlags(..), CppFlags(..) ) where import Config.Type import Config.Read import Idea import Apply import HLint import HSE.All import Hint.All import CmdLine import Paths_hlint import Data.List.Extra import Data.Maybe import System.FilePath -- | Get the Cabal configured data directory of HLint. getHLintDataDir :: IO FilePath getHLintDataDir = getDataDir -- | The function produces a tuple containg 'ParseFlags' (for 'parseModuleEx'), -- and 'Classify' and 'Hint' for 'applyHints'. -- It approximates the normal HLint configuration steps, roughly: -- -- 1. Use 'findSettings' with 'readSettingsFile' to find and load the HLint settings files. -- -- 1. Use 'parseFlagsAddFixities' and 'resolveHints' to transform the outputs of 'findSettings'. -- -- If you want to do anything custom (e.g. using a different data directory, storing intermediate outputs, -- loading hints from a database) you are expected to copy and paste this function, then change it to your needs. autoSettings :: IO (ParseFlags, [Classify], Hint) autoSettings = do (fixities, classify, hints) <- findSettings (readSettingsFile Nothing) Nothing return (parseFlagsAddFixities fixities defaultParseFlags, classify, resolveHints hints) -- | A version of 'autoSettings' which respects some of the arguments supported by HLint. -- If arguments unrecognised by HLint are used it will result in an error. -- Arguments which have no representation in the return type are silently ignored. argsSettings :: [String] -> IO (ParseFlags, [Classify], Hint) argsSettings args = do cmd <- getCmd args case cmd of CmdMain{..} -> do -- FIXME: Two things that could be supported (but aren't) are 'cmdGivenHints' and 'cmdWithHints'. (_,settings) <- readAllSettings args cmd let (fixities, classify, hints) = splitSettings settings let flags = parseFlagsSetLanguage (cmdExtensions cmd) $ parseFlagsAddFixities fixities $ defaultParseFlags{cppFlags = cmdCpp cmd} let ignore = [Classify Ignore x "" "" | x <- cmdIgnore] return (flags, classify ++ ignore, resolveHints hints) _ -> error "Can only invoke autoSettingsArgs with the root process" -- | Given a directory (or 'Nothing' to imply 'getHLintDataDir'), and a module name -- (e.g. @HLint.Default@), find the settings file associated with it, returning the -- name of the file, and (optionally) the contents. -- -- This function looks for all settings files starting with @HLint.@ in the directory -- argument, and all other files relative to the current directory. readSettingsFile :: Maybe FilePath -> String -> IO (FilePath, Maybe String) readSettingsFile dir x | takeExtension x `elem` [".yml",".yaml"] = do dir <- maybe getHLintDataDir return dir return (dir x, Nothing) | Just x <- "HLint." `stripPrefix` x = do dir <- maybe getHLintDataDir return dir return (dir x <.> "hs", Nothing) | otherwise = return (x <.> "hs", Nothing) -- | Given a function to load a module (typically 'readSettingsFile'), and a module to start from -- (defaults to @hlint.yaml@) find the information from all settings files. findSettings :: (String -> IO (FilePath, Maybe String)) -> Maybe String -> IO ([Fixity], [Classify], [Either HintBuiltin HintRule]) findSettings load start = do (file,contents) <- load $ fromMaybe "hlint.yaml" start xs <- readFilesConfig [(file,contents)] return $ splitSettings xs -- | Split a list of 'Setting' for separate use in parsing and hint resolution splitSettings :: [Setting] -> ([Fixity], [Classify], [Either HintBuiltin HintRule]) splitSettings xs = ([x | Infix x <- xs] ,[x | SettingClassify x <- xs] ,[Right x | SettingMatchExp x <- xs] ++ map Left [minBound..maxBound]) -- | Snippet from the documentation, if this changes, update the documentation _docs :: IO () _docs = do (flags, classify, hint) <- autoSettings Right m <- parseModuleEx flags "MyFile.hs" Nothing print $ applyHints classify hint [m] hlint-2.0.11/src/Language/Haskell/HLint.hs0000644000000000000000000000267313210071537016355 0ustar0000000000000000{-| /WARNING: This module represents the old version of the HLint API./ /It will be deleted in favour of "Language.Haskell.HLint3" in the next major version./ This module provides a library interface to HLint, strongly modelled on the command line interface. -} module Language.Haskell.HLint(hlint, Suggestion, suggestionLocation, suggestionSeverity, Severity(..)) where import qualified HLint import Config.Type import Idea import HSE.All -- | This function takes a list of command line arguments, and returns the given suggestions. -- To see a list of arguments type @hlint --help@ at the console. -- This function writes to the stdout/stderr streams, unless @--quiet@ is specified. -- -- As an example: -- -- > do hints <- hlint ["src", "--ignore=Use map","--quiet"] -- > when (length hints > 3) $ error "Too many hints!" hlint :: [String] -> IO [Suggestion] hlint = fmap (map Suggestion_) . HLint.hlint -- | A suggestion - the @Show@ instance is of particular use. newtype Suggestion = Suggestion_ {fromSuggestion :: Idea} deriving (Eq,Ord) instance Show Suggestion where show = show . fromSuggestion -- | From a suggestion, extract the file location it refers to. suggestionLocation :: Suggestion -> SrcLoc suggestionLocation = getPointLoc . ideaSpan . fromSuggestion -- | From a suggestion, determine how severe it is. suggestionSeverity :: Suggestion -> Severity suggestionSeverity = ideaSeverity . fromSuggestion hlint-2.0.11/src/HSE/0000755000000000000000000000000013210071537012304 5ustar0000000000000000hlint-2.0.11/src/HSE/Util.hs0000644000000000000000000002437613210071537013571 0ustar0000000000000000{-# LANGUAGE FlexibleContexts, ViewPatterns #-} module HSE.Util(module HSE.Util, def) where import Control.Monad import Data.Default import Data.List import Language.Haskell.Exts.Util() -- Orphan instances of Default for SrcLoc etc import Data.Maybe import Data.Data hiding (Fixity) import System.FilePath import HSE.Type import Data.Functor import Prelude --------------------------------------------------------------------- -- ACCESSOR/TESTER ellipses :: QName S ellipses = UnQual an $ Ident an "..." -- Must be an Ident, not a Symbol opExp :: QOp S -> Exp_ opExp (QVarOp s op) = Var s op opExp (QConOp s op) = Con s op expOp :: Exp_ -> Maybe (QOp S) expOp (Var s op) = Just $ QVarOp s op expOp (Con s op) = Just $ QConOp s op expOp _ = Nothing moduleDecls :: Module_ -> [Decl_] moduleDecls (Module _ _ _ _ xs) = xs moduleDecls _ = [] -- XmlPage/XmlHybrid moduleName :: Module_ -> String moduleName (Module _ Nothing _ _ _) = "Main" moduleName (Module _ (Just (ModuleHead _ (ModuleName _ x) _ _)) _ _ _) = x moduleName _ = "" -- XmlPage/XmlHybrid moduleImports :: Module_ -> [ImportDecl S] moduleImports (Module _ _ _ x _) = x moduleImports _ = [] -- XmlPage/XmlHybrid modulePragmas :: Module_ -> [ModulePragma S] modulePragmas (Module _ _ x _ _) = x modulePragmas _ = [] -- XmlPage/XmlHybrid fromModuleName :: ModuleName S -> String fromModuleName (ModuleName _ x) = x fromChar :: Exp_ -> Maybe Char fromChar (Lit _ (Char _ x _)) = Just x fromChar _ = Nothing fromPChar :: Pat_ -> Maybe Char fromPChar (PLit _ _ (Char _ x _)) = Just x fromPChar _ = Nothing fromString :: Exp_ -> Maybe String fromString (Lit _ (String _ x _)) = Just x fromString _ = Nothing fromPString :: Pat_ -> Maybe String fromPString (PLit _ _ (String _ x _)) = Just x fromPString _ = Nothing fromParen :: Exp_ -> Exp_ fromParen (Paren _ x) = fromParen x fromParen x = x fromPParen :: Pat s -> Pat s fromPParen (PParen _ x) = fromPParen x fromPParen x = x fromTyParen :: Type s -> Type s fromTyParen (TyParen _ x) = fromTyParen x fromTyParen x = x fromTyBang :: Type s -> Type s fromTyBang (TyBang _ _ _ x) = x fromTyBang x = x fromDeriving :: Deriving s -> [InstRule s] fromDeriving (Deriving _ x) = x -- is* :: Exp_ -> Bool -- is* :: Decl_ -> Bool isVar Var{} = True; isVar _ = False isCon Con{} = True; isCon _ = False isApp App{} = True; isApp _ = False isInfixApp InfixApp{} = True; isInfixApp _ = False isAnyApp x = isApp x || isInfixApp x isParen Paren{} = True; isParen _ = False isIf If{} = True; isIf _ = False isLambda Lambda{} = True; isLambda _ = False isMDo MDo{} = True; isMDo _ = False isBoxed Boxed{} = True; isBoxed _ = False isDerivDecl DerivDecl{} = True; isDerivDecl _ = False isPBangPat PBangPat{} = True; isPBangPat _ = False isPFieldPun PFieldPun{} = True; isPFieldPun _ = False isFieldPun FieldPun{} = True; isFieldPun _ = False isPWildCard PWildCard{} = True; isPWildCard _ = False isPFieldWildcard PFieldWildcard{} = True; isPFieldWildcard _ = False isFieldWildcard FieldWildcard{} = True; isFieldWildcard _ = False isPViewPat PViewPat{} = True; isPViewPat _ = False isParComp ParComp{} = True; isParComp _ = False isTypeApp TypeApp{} = True; isTypeApp _ = False isPatTypeSig PatTypeSig{} = True; isPatTypeSig _ = False isQuasiQuote QuasiQuote{} = True; isQuasiQuote _ = False isTyQuasiQuote TyQuasiQuote{} = True; isTyQuasiQuote _ = False isSpliceDecl SpliceDecl{} = True; isSpliceDecl _ = False isNewType NewType{} = True; isNewType _ = False isRecStmt RecStmt{} = True; isRecStmt _ = False isClsDefSig ClsDefSig{} = True; isClsDefSig _ = False isTyBang TyBang{} = True; isTyBang _ = False isLCase LCase{} = True; isLCase _ = False isTupleSection TupleSection{} = True; isTupleSection _ = False isString String{} = True; isString _ = False isSection LeftSection{} = True isSection RightSection{} = True isSection _ = False isPrimLiteral PrimInt{} = True isPrimLiteral PrimWord{} = True isPrimLiteral PrimFloat{} = True isPrimLiteral PrimDouble{} = True isPrimLiteral PrimChar{} = True isPrimLiteral PrimString{} = True isPrimLiteral _ = False allowRightSection x = x `notElem` ["-","#"] allowLeftSection x = x /= "#" unqual :: QName S -> QName S unqual (Qual an _ x) = UnQual an x unqual x = x fromQual :: QName a -> Maybe (Name a) fromQual (Qual _ _ x) = Just x fromQual (UnQual _ x) = Just x fromQual _ = Nothing isSpecial :: QName S -> Bool isSpecial Special{} = True; isSpecial _ = False isDol :: QOp S -> Bool isDol (QVarOp _ (UnQual _ (Symbol _ "$"))) = True isDol _ = False isDot :: QOp S -> Bool isDot (QVarOp _ (UnQual _ (Symbol _ "."))) = True isDot _ = False isDotApp :: Exp_ -> Bool isDotApp (InfixApp _ _ dot _) | isDot dot = True isDotApp _ = False dotApp :: Exp_ -> Exp_ -> Exp_ dotApp x = InfixApp an x (QVarOp an $ UnQual an $ Symbol an ".") dotApps :: [Exp_] -> Exp_ dotApps [] = error "HSE.Util.dotApps, does not work on an empty list" dotApps [x] = x dotApps (x:xs) = dotApp x (dotApps xs) isLexeme Var{} = True isLexeme Con{} = True isLexeme Lit{} = True isLexeme _ = False isAssocLeft AssocLeft{} = True; isAssocLeft _ = False isAssocNone AssocNone{} = True; isAssocNone _ = False isWHNF :: Exp_ -> Bool isWHNF Con{} = True isWHNF (Lit _ x) = case x of String{} -> False; Int{} -> False; Frac{} -> False; _ -> True isWHNF Lambda{} = True isWHNF Tuple{} = True isWHNF List{} = True isWHNF (Paren _ x) = isWHNF x isWHNF (ExpTypeSig _ x _) = isWHNF x -- other (unknown) constructors may have bang patterns in them, so approximate isWHNF (App _ c@Con{} _) | prettyPrint c `elem` ["Just","Left","Right"] = True isWHNF _ = False --------------------------------------------------------------------- -- HSE FUNCTIONS isKindHash :: Type_ -> Bool isKindHash (TyParen _ x) = isKindHash x isKindHash (TyApp _ x _) = isKindHash x isKindHash (TyCon _ (fromQual -> Just (Ident _ s))) = "#" `isSuffixOf` s isKindHash _ = False getEquations :: Decl s -> [Decl s] getEquations (FunBind s xs) = map (FunBind s . (:[])) xs getEquations x@PatBind{} = [toFunBind x] getEquations x = [x] toFunBind :: Decl s -> Decl s toFunBind (PatBind s (PVar _ name) bod bind) = FunBind s [Match s name [] bod bind] toFunBind x = x -- case and if both have branches, nothing else does replaceBranches :: Exp s -> ([Exp s], [Exp s] -> Exp s) replaceBranches (If s a b c) = ([b,c], \[b,c] -> If s a b c) replaceBranches (Case s a bs) = (concatMap f bs, Case s a . g bs) where f (Alt _ _ (UnGuardedRhs _ x) _) = [x] f (Alt _ _ (GuardedRhss _ xs) _) = [x | GuardedRhs _ _ x <- xs] g (Alt s1 a (UnGuardedRhs s2 _) b:rest) (x:xs) = Alt s1 a (UnGuardedRhs s2 x) b : g rest xs g (Alt s1 a (GuardedRhss s2 ns) b:rest) xs = Alt s1 a (GuardedRhss s2 [GuardedRhs a b x | (GuardedRhs a b _,x) <- zip ns as]) b : g rest bs where (as,bs) = splitAt (length ns) xs g [] [] = [] g _ _ = error "HSE.Util.replaceBranches: internal invariant failed, lists are of differing lengths" replaceBranches x = ([], \[] -> x) --------------------------------------------------------------------- -- VECTOR APPLICATION apps :: [Exp_] -> Exp_ apps = foldl1 (App an) fromApps :: Exp_ -> [Exp_] fromApps = map fst . fromAppsWithLoc fromAppsWithLoc :: Exp_ -> [(Exp_, S)] fromAppsWithLoc (App l x y) = fromAppsWithLoc x ++ [(y, l)] fromAppsWithLoc x = [(x, ann x)] -- Rule for the Uniplate Apps functions -- Given (f a) b, consider the children to be: children f ++ [a,b] childrenApps :: Exp_ -> [Exp_] childrenApps (App s x y) = childrenApps x ++ [y] childrenApps x = children x descendApps :: (Exp_ -> Exp_) -> Exp_ -> Exp_ descendApps f (App s x y) = App s (descendApps f x) (f y) descendApps f x = descend f x descendAppsM :: Monad m => (Exp_ -> m Exp_) -> Exp_ -> m Exp_ descendAppsM f (App s x y) = liftM2 (App s) (descendAppsM f x) (f y) descendAppsM f x = descendM f x universeApps :: Exp_ -> [Exp_] universeApps x = x : concatMap universeApps (childrenApps x) transformApps :: (Exp_ -> Exp_) -> Exp_ -> Exp_ transformApps f = f . descendApps (transformApps f) transformAppsM :: Monad m => (Exp_ -> m Exp_) -> Exp_ -> m Exp_ transformAppsM f x = f =<< descendAppsM (transformAppsM f) x --------------------------------------------------------------------- -- UNIPLATE FUNCTIONS universeS :: (Data x, Data (f S)) => x -> [f S] universeS = universeBi childrenS :: (Data x, Data (f S)) => x -> [f S] childrenS = childrenBi -- return the parent along with the child universeParentExp :: Data a => a -> [(Maybe (Int, Exp_), Exp_)] universeParentExp xs = concat [(Nothing, x) : f x | x <- childrenBi xs] where f p = concat [(Just (i,p), c) : f c | (i,c) <- zip [0..] $ children p] --------------------------------------------------------------------- -- SRCLOC FUNCTIONS showSrcLoc :: SrcLoc -> String showSrcLoc (SrcLoc file line col) = take 1 file ++ f (drop 1 file) ++ ":" ++ show line ++ ":" ++ show col where f (x:y:zs) | isPathSeparator x && isPathSeparator y = f $ x:zs f (x:xs) = x : f xs f [] = [] an :: SrcSpanInfo an = def dropAnn :: Functor f => f SrcSpanInfo -> f () dropAnn = void --------------------------------------------------------------------- -- SRCLOC EQUALITY -- enforce all being on S, as otherwise easy to =~= on a Just, and get the wrong functor x /=~= y = not $ x =~= y elem_, notElem_ :: (Annotated f, Eq (f ())) => f S -> [f S] -> Bool elem_ x = any (x =~=) notElem_ x = not . elem_ x nub_ :: (Annotated f, Eq (f ())) => [f S] -> [f S] nub_ = nubBy (=~=) delete_ :: (Annotated f, Eq (f ())) => f S -> [f S] -> [f S] delete_ = deleteBy (=~=) intersect_ :: (Annotated f, Eq (f ())) => [f S] -> [f S] -> [f S] intersect_ = intersectBy (=~=) eqList, neqList :: (Annotated f, Eq (f ())) => [f S] -> [f S] -> Bool neqList x y = not $ eqList x y eqList (x:xs) (y:ys) = x =~= y && eqList xs ys eqList [] [] = True eqList _ _ = False eqMaybe:: (Annotated f, Eq (f ())) => Maybe (f S) -> Maybe (f S) -> Bool eqMaybe (Just x) (Just y) = x =~= y eqMaybe Nothing Nothing = True eqMaybe _ _ = False --------------------------------------------------------------------- -- FIXITIES getFixity :: Decl a -> [Fixity] getFixity (InfixDecl sl a mp ops) = [Fixity (void a) (fromMaybe 9 mp) (UnQual () $ void $ f op) | op <- ops] where f (VarOp _ x) = x f (ConOp _ x) = x getFixity _ = [] toInfixDecl :: Fixity -> Decl () toInfixDecl (Fixity a b c) = InfixDecl () a (Just b) $ maybeToList $ VarOp () <$> fromQual c hlint-2.0.11/src/HSE/Unify.hs0000644000000000000000000001067313210071537013741 0ustar0000000000000000{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables #-} module HSE.Unify( Subst, fromSubst, validSubst, substitute, unifyExp, ) where import Control.Applicative import Data.List.Extra import Data.Maybe import Data.Data import Data.Monoid import Config.Type import Hint.Type import Control.Monad import Data.Tuple.Extra import Util import Prelude --------------------------------------------------------------------- -- SUBSTITUTION DATA TYPE -- | A list of substitutions. A key may be duplicated, you need to call 'check' -- to ensure the substitution is valid. newtype Subst a = Subst [(String, a)] -- | Unpack the substitution fromSubst :: Subst a -> [(String, a)] fromSubst (Subst xs) = xs instance Functor Subst where fmap f (Subst xs) = Subst $ map (second f) xs instance Pretty a => Show (Subst a) where show (Subst xs) = unlines [a ++ " = " ++ prettyPrint b | (a,b) <- xs] instance Monoid (Subst a) where mempty = Subst [] mappend (Subst xs) (Subst ys) = Subst $ xs ++ ys -- check the unification is valid and simplify it validSubst :: (a -> a -> Bool) -> Subst a -> Maybe (Subst a) validSubst eq = fmap Subst . mapM f . groupSort . fromSubst where f (x,y:ys) | all (eq y) ys = Just (x,y) f _ = Nothing -- | Perform a substitution substitute :: Subst Exp_ -> Exp_ -> Exp_ substitute (Subst bind) = transformBracket exp . transformBi pat where exp (Var _ (fromNamed -> x)) = lookup x bind exp _ = Nothing pat (PVar _ (fromNamed -> x)) | Just y <- lookup x bind = toNamed $ fromNamed y pat x = x :: Pat_ --------------------------------------------------------------------- -- UNIFICATION type NameMatch = QName S -> QName S -> Bool nmOp :: NameMatch -> QOp S -> QOp S -> Bool nmOp nm (QVarOp _ x) (QVarOp _ y) = nm x y nmOp nm (QConOp _ x) (QConOp _ y) = nm x y nmOp nm _ _ = False -- | Unification, obeys the property that if @unify a b = s@, then @substitute s a = b@. unify :: Data a => NameMatch -> Bool -> a -> a -> Maybe (Subst Exp_) unify nm root x y | Just (x,y) <- cast (x,y) = unifyExp nm root x y | Just (x,y) <- cast (x,y) = unifyPat nm x y | Just (x :: S) <- cast x = Just mempty | otherwise = unifyDef nm x y unifyDef :: Data a => NameMatch -> a -> a -> Maybe (Subst Exp_) unifyDef nm x y = fmap mconcat . sequence =<< gzip (unify nm False) x y -- App/InfixApp are analysed specially for performance reasons -- root = True, this is the outside of the expr -- do not expand out a dot at the root, since otherwise you get two matches because of readRule (Bug #570) unifyExp :: NameMatch -> Bool -> Exp_ -> Exp_ -> Maybe (Subst Exp_) unifyExp nm root x y | isParen x || isParen y = fmap (rebracket y) <$> unifyExp nm root (fromParen x) (fromParen y) where rebracket (Paren l e') e | e' == e = Paren l e rebracket e e' = e' unifyExp nm root (Var _ (fromNamed -> v)) y | isUnifyVar v = Just $ Subst [(v,y)] unifyExp nm root (Var _ x) (Var _ y) | nm x y = Just mempty -- Options: match directly, and expand through . unifyExp nm root x@(App _ x1 x2) (App _ y1 y2) = liftM2 (<>) (unifyExp nm False x1 y1) (unifyExp nm False x2 y2) `mplus` (do guard $ not root -- don't expand . if at the root, otherwise you can get duplicate matches -- because the matching engine auto-generates hints in dot-form InfixApp _ y11 dot y12 <- return $ fromParen y1 guard $ isDot dot unifyExp nm root x (App an y11 (App an y12 y2))) -- Options: match directly, then expand through $, then desugar infix unifyExp nm root x (InfixApp _ lhs2 op2 rhs2) | InfixApp _ lhs1 op1 rhs1 <- x = guard (nmOp nm op1 op2) >> liftM2 (<>) (unifyExp nm False lhs1 lhs2) (unifyExp nm False rhs1 rhs2) | isDol op2 = unifyExp nm root x $ App an lhs2 rhs2 | otherwise = unifyExp nm root x $ App an (App an (opExp op2) lhs2) rhs2 unifyExp nm root x y | isOther x, isOther y = unifyDef nm x y where -- types that are not already handled in unify {-# INLINE isOther #-} isOther Var{} = False isOther App{} = False isOther InfixApp{} = False isOther _ = True unifyExp nm root _ _ = Nothing unifyPat :: NameMatch -> Pat_ -> Pat_ -> Maybe (Subst Exp_) unifyPat nm (PVar _ x) (PVar _ y) = Just $ Subst [(fromNamed x, toNamed $ fromNamed y)] unifyPat nm (PVar _ x) PWildCard{} = Just $ Subst [(fromNamed x, toNamed $ "_" ++ fromNamed x)] unifyPat nm x y = unifyDef nm x y hlint-2.0.11/src/HSE/Type.hs0000644000000000000000000000075513210071537013570 0ustar0000000000000000 module HSE.Type( S, Module_, Decl_, Exp_, Pat_, Type_, module HSE, module Uniplate ) where -- Almost all from the Annotated module, but the fixity resolution from Annotated -- uses the unannotated Assoc enumeration, so export that instead import Language.Haskell.Exts as HSE hiding (parse, loc, paren) import Data.Generics.Uniplate.Data as Uniplate type S = SrcSpanInfo type Module_ = Module S type Decl_ = Decl S type Exp_ = Exp S type Pat_ = Pat S type Type_ = Type S hlint-2.0.11/src/HSE/Scope.hs0000644000000000000000000000732613210071537013721 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} module HSE.Scope( Scope, scopeCreate, scopeImports, scopeMatch, scopeMove ) where import Data.Monoid import HSE.Type import HSE.Util import Data.List import Data.Maybe import Prelude {- the hint file can do: import Prelude (filter) import Data.List (filter) import List (filter) then filter on it's own will get expanded to all of them import Data.List import List as Data.List if Data.List.head x ==> x, then that might match List too -} -- | Data type representing the modules in scope within a module. -- Created with 'scopeCreate' and queried with 'scopeMatch' and 'scopeMove'. -- Note that the 'mempty' 'Scope' is not equivalent to 'scopeCreate' on an empty module, -- due to the implicit import of 'Prelude'. newtype Scope = Scope [ImportDecl S] deriving Show instance Monoid Scope where mempty = Scope [] mappend (Scope xs) (Scope ys) = Scope $ xs ++ ys -- | Create a 'Scope' value from a module, based on the modules imports. scopeCreate :: Module SrcSpanInfo -> Scope scopeCreate xs = Scope $ [prelude | not $ any isPrelude res] ++ res where res = [x | x <- moduleImports xs, importPkg x /= Just "hint"] prelude = ImportDecl an (ModuleName an "Prelude") False False False Nothing Nothing Nothing isPrelude x = fromModuleName (importModule x) == "Prelude" scopeImports :: Scope -> [ImportDecl S] scopeImports (Scope x) = x -- | Given a two names in scopes, could they possibly refer to the same thing. -- This property is reflexive. scopeMatch :: (Scope, QName S) -> (Scope, QName S) -> Bool scopeMatch (a, x@Special{}) (b, y@Special{}) = x =~= y scopeMatch (a, x) (b, y) | isSpecial x || isSpecial y = False scopeMatch (a, x) (b, y) = unqual x =~= unqual y && not (null $ possModules a x `intersect` possModules b y) -- | Given a name in a scope, and a new scope, create a name for the new scope that will refer -- to the same thing. If the resulting name is ambiguous, it picks a plausible candidate. scopeMove :: (Scope, QName S) -> Scope -> QName S scopeMove (a, x@(fromQual -> Just name)) (Scope b) | null imps = head $ real ++ [x] | any (not . importQualified) imps = unqual x | otherwise = Qual an (head $ mapMaybe importAs imps ++ map importModule imps) name where real = [Qual an (ModuleName an m) name | m <- possModules a x] imps = [i | r <- real, i <- b, possImport i r] scopeMove (_, x) _ = x -- which modules could a name possibly lie in -- if it's qualified but not matching any import, assume the user -- just lacks an import possModules :: Scope -> QName S -> [String] possModules (Scope is) x = f x where res = [fromModuleName $ importModule i | i <- is, possImport i x] f Special{} = [""] f x@(Qual _ mod _) = [fromModuleName mod | null res] ++ res f _ = res possImport :: ImportDecl S -> QName S -> Bool possImport i Special{} = False possImport i (Qual _ mod x) = fromModuleName mod `elem` map fromModuleName ms && possImport i{importQualified=False} (UnQual an x) where ms = importModule i : maybeToList (importAs i) possImport i (UnQual _ x) = not (importQualified i) && maybe True f (importSpecs i) where f (ImportSpecList _ hide xs) = if hide then Just True `notElem` ms else Nothing `elem` ms || Just True `elem` ms where ms = map g xs g :: ImportSpec S -> Maybe Bool -- does this import cover the name x g (IVar _ y) = Just $ x =~= y g (IAbs _ _ y) = Just $ x =~= y g (IThingAll _ y) = if x =~= y then Just True else Nothing g (IThingWith _ y ys) = Just $ x `elem_` (y : map fromCName ys) fromCName :: CName S -> Name S fromCName (VarName _ x) = x fromCName (ConName _ x) = x hlint-2.0.11/src/HSE/Reduce.hs0000644000000000000000000000155613210071537014056 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} -- Evaluate/reduce a HSE Exp as much as possible module HSE.Reduce(reduce) where import HSE.Match import HSE.Util import HSE.Type import Language.Haskell.Exts.Util reduce :: Exp_ -> Exp_ reduce = fromParen . transform reduce1 reduce1 :: Exp_ -> Exp_ reduce1 (App s len (Lit _ (String _ xs _))) | len ~= "length" = Lit s $ Int s n (show n) where n = fromIntegral $ length xs reduce1 (App s len (List _ xs)) | len ~= "length" = Lit s $ Int s n (show n) where n = fromIntegral $ length xs reduce1 (view -> App2 op (Lit _ x) (Lit _ y)) | op ~= "==" = toNamed $ show $ x =~= y reduce1 (view -> App2 op (Lit _ (Int _ x _)) (Lit _ (Int _ y _))) | op ~= ">=" = toNamed $ show $ x >= y reduce1 (view -> App2 op x y) | op ~= "&&" && x ~= "True" = y | op ~= "&&" && x ~= "False" = x reduce1 (Paren _ x) | isAtom x = x reduce1 x = x hlint-2.0.11/src/HSE/Match.hs0000644000000000000000000001046313210071537013700 0ustar0000000000000000{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances #-} module HSE.Match( View(..), Named(..), (~=), isSym, App2(App2), LamConst1(LamConst1), PVar_(PVar_), Var_(Var_), PApp_(PApp_) ) where import Data.Char import HSE.Type import HSE.Util class View a b where view :: a -> b data App2 = NoApp2 | App2 Exp_ Exp_ Exp_ deriving Show instance View Exp_ App2 where view (fromParen -> InfixApp _ lhs op rhs) = App2 (opExp op) lhs rhs view (fromParen -> App _ (fromParen -> App _ f x) y) = App2 f x y view _ = NoApp2 data App1 = NoApp1 | App1 Exp_ Exp_ deriving Show instance View Exp_ App1 where view (fromParen -> App _ f x) = App1 f x view _ = NoApp1 -- \_ -> body data LamConst1 = NoLamConst1 | LamConst1 Exp_ deriving Show instance View Exp_ LamConst1 where view (fromParen -> Lambda _ [PWildCard _] x) = LamConst1 x view _ = NoLamConst1 data PApp_ = NoPApp_ | PApp_ String [Pat_] instance View Pat_ PApp_ where view (fromPParen -> PApp _ x xs) = PApp_ (fromNamed x) xs view (fromPParen -> PInfixApp _ lhs op rhs) = PApp_ (fromNamed op) [lhs, rhs] view _ = NoPApp_ data PVar_ = NoPVar_ | PVar_ String instance View Pat_ PVar_ where view (fromPParen -> PVar _ x) = PVar_ $ fromNamed x view _ = NoPVar_ data Var_ = NoVar_ | Var_ String deriving Eq instance View Exp_ Var_ where view (fromParen -> Var _ (UnQual _ x)) = Var_ $ fromNamed x view _ = NoVar_ (~=) :: Named a => a -> String -> Bool (~=) = (==) . fromNamed -- | fromNamed will return \"\" when it cannot be represented -- toNamed may crash on \"\" class Named a where toNamed :: String -> a fromNamed :: a -> String isCtor (x:_) = isUpper x || x == ':' isCtor _ = False isSym (x:_) = not $ isAlpha x || x `elem` "_'" isSym _ = False instance Named (Exp S) where fromNamed (Var _ x) = fromNamed x fromNamed (Con _ x) = fromNamed x fromNamed (List _ []) = "[]" fromNamed _ = "" toNamed "[]" = List an [] toNamed x | isCtor x = Con an $ toNamed x | otherwise = Var an $ toNamed x instance Named (QName S) where fromNamed (Special _ Cons{}) = ":" fromNamed (Special _ UnitCon{}) = "()" fromNamed (UnQual _ x) = fromNamed x fromNamed _ = "" toNamed ":" = Special an $ Cons an toNamed x = UnQual an $ toNamed x instance Named (Name S) where fromNamed (Ident _ x) = x fromNamed (Symbol _ x) = x toNamed x | isSym x = Symbol an x | otherwise = Ident an x instance Named (ModuleName S) where fromNamed (ModuleName _ x) = x toNamed = ModuleName an instance Named (Pat S) where fromNamed (PVar _ x) = fromNamed x fromNamed (PApp _ x []) = fromNamed x fromNamed (PList _ []) = "[]" fromNamed _ = "" toNamed x | isCtor x = PApp an (toNamed x) [] | otherwise = PVar an $ toNamed x instance Named (TyVarBind S) where fromNamed (KindedVar _ x _) = fromNamed x fromNamed (UnkindedVar _ x) = fromNamed x toNamed x = UnkindedVar an (toNamed x) instance Named (QOp S) where fromNamed (QVarOp _ x) = fromNamed x fromNamed (QConOp _ x) = fromNamed x toNamed x | isCtor x = QConOp an $ toNamed x | otherwise = QVarOp an $ toNamed x instance Named (Match S) where fromNamed (Match _ x _ _ _) = fromNamed x fromNamed (InfixMatch _ _ x _ _ _) = fromNamed x toNamed = error "No toNamed for Match" instance Named (DeclHead S) where fromNamed (DHead _ x) = fromNamed x fromNamed (DHInfix _ _ x) = fromNamed x fromNamed (DHParen _ x) = fromNamed x fromNamed (DHApp _ x _) = fromNamed x toNamed = error "No toNamed for DeclHead" instance Named (Decl S) where fromNamed (TypeDecl _ name _) = fromNamed name fromNamed (DataDecl _ _ _ name _ _) = fromNamed name fromNamed (GDataDecl _ _ _ name _ _ _) = fromNamed name fromNamed (TypeFamDecl _ name _ _) = fromNamed name fromNamed (DataFamDecl _ _ name _) = fromNamed name fromNamed (ClassDecl _ _ name _ _) = fromNamed name fromNamed (PatBind _ (PVar _ name) _ _) = fromNamed name fromNamed (FunBind _ (name:_)) = fromNamed name fromNamed (ForImp _ _ _ _ name _) = fromNamed name fromNamed (ForExp _ _ _ name _) = fromNamed name fromNamed (TypeSig _ (name:_) _) = fromNamed name fromNamed _ = "" toNamed = error "No toNamed for Decl" hlint-2.0.11/src/HSE/All.hs0000644000000000000000000001532413210071537013355 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ViewPatterns #-} module HSE.All( module X, CppFlags(..), ParseFlags(..), defaultParseFlags, parseFlagsAddFixities, parseFlagsSetLanguage, parseModuleEx, ParseError(..), freeVars, vars, varss, pvars ) where import Language.Haskell.Exts.Util hiding (freeVars, Vars(..)) import qualified Language.Haskell.Exts.Util as X import HSE.Util as X import HSE.Reduce as X import HSE.Type as X import HSE.Match as X import HSE.Scope as X import Util import Data.Char import Data.List.Extra import Data.Maybe import Language.Preprocessor.Cpphs import Data.Set (Set) import qualified Data.Map as Map import qualified Data.Set as Set import System.IO.Extra import Data.Functor import Prelude vars :: FreeVars a => a -> [String] freeVars :: FreeVars a => a -> Set String varss, pvars :: AllVars a => a -> [String] vars = Set.toList . Set.map prettyPrint . X.freeVars varss = Set.toList . Set.map prettyPrint . X.free . X.allVars pvars = Set.toList . Set.map prettyPrint . X.bound . X.allVars freeVars = Set.map prettyPrint . X.freeVars -- | What C pre processor should be used. data CppFlags = NoCpp -- ^ No pre processing is done. | CppSimple -- ^ Lines prefixed with @#@ are stripped. | Cpphs CpphsOptions -- ^ The @cpphs@ library is used. -- | Created with 'defaultParseFlags', used by 'parseModuleEx'. data ParseFlags = ParseFlags {cppFlags :: CppFlags -- ^ How the file is preprocessed (defaults to 'NoCpp'). ,hseFlags :: ParseMode -- ^ How the file is parsed (defaults to all fixities in the @base@ package and most non-conflicting extensions). } -- | Default value for 'ParseFlags'. defaultParseFlags :: ParseFlags defaultParseFlags = ParseFlags NoCpp defaultParseMode{fixities=Just baseFixities, ignoreLinePragmas=False, ignoreFunctionArity=True, extensions=defaultExtensions} parseFlagsNoLocations :: ParseFlags -> ParseFlags parseFlagsNoLocations x = x{cppFlags = case cppFlags x of Cpphs y -> Cpphs $ f y; y -> y} where f x = x{boolopts = (boolopts x){locations=False}} -- | Given some fixities, add them to the existing fixities in 'ParseFlags'. parseFlagsAddFixities :: [Fixity] -> ParseFlags -> ParseFlags parseFlagsAddFixities fx x = x{hseFlags=hse{fixities = Just $ fx ++ fromMaybe [] (fixities hse)}} where hse = hseFlags x parseFlagsSetLanguage :: (Language, [Extension]) -> ParseFlags -> ParseFlags parseFlagsSetLanguage (l, es) x = x{hseFlags=(hseFlags x){baseLanguage = l, extensions = es}} runCpp :: CppFlags -> FilePath -> String -> IO String runCpp NoCpp _ x = return x runCpp CppSimple _ x = return $ unlines [if "#" `isPrefixOf` trimStart x then "" else x | x <- lines x] runCpp (Cpphs o) file x = dropLine <$> runCpphs o file x where -- LINE pragmas always inserted when locations=True dropLine (line1 -> (a,b)) | "{-# LINE " `isPrefixOf` a = b dropLine x = x --------------------------------------------------------------------- -- PARSING -- | A parse error from 'parseModuleEx'. data ParseError = ParseError {parseErrorLocation :: SrcLoc -- ^ Location of the error. ,parseErrorMessage :: String -- ^ Message about the cause of the error. ,parseErrorContents :: String -- ^ Snippet of several lines (typically 5) including a @>@ character pointing at the faulty line. } -- | Parse a Haskell module. Applies the C pre processor, and uses best-guess fixity resolution if there are ambiguities. -- The filename @-@ is treated as @stdin@. Requires some flags (often 'defaultParseFlags'), the filename, and optionally the contents of that file. parseModuleEx :: ParseFlags -> FilePath -> Maybe String -> IO (Either ParseError (Module SrcSpanInfo, [Comment])) parseModuleEx flags file str = do str <- case str of Just x -> return x Nothing | file == "-" -> getContents | otherwise -> readFileUTF8' file str <- return $ fromMaybe str $ stripPrefix "\65279" str -- remove the BOM if it exists, see #130 ppstr <- runCpp (cppFlags flags) file str case parseFileContentsWithComments (mode flags) ppstr of ParseOk (x, cs) -> return $ Right (applyFixity fixity x, cs) ParseFailed sl msg -> do -- figure out the best line number to grab context from, by reparsing -- but not generating {-# LINE #-} pragmas flags <- return $ parseFlagsNoLocations flags ppstr2 <- runCpp (cppFlags flags) file str let pe = case parseFileContentsWithMode (mode flags) ppstr2 of ParseFailed sl2 _ -> context (srcLine sl2) ppstr2 _ -> context (srcLine sl) ppstr return $ Left $ ParseError sl msg pe where fixity = fromMaybe [] $ fixities $ hseFlags flags mode flags = (hseFlags flags) {parseFilename = file ,fixities = Nothing } -- | Given a line number, and some source code, put bird ticks around the appropriate bit. context :: Int -> String -> String context lineNo src = unlines $ dropWhileEnd (all isSpace) $ dropWhile (all isSpace) $ zipWith (++) ticks $ take 5 $ drop (lineNo - 3) $ lines src ++ ["","","","",""] where ticks = [" "," ","> "," "," "] --------------------------------------------------------------------- -- FIXITIES -- resolve fixities later, so we don't ever get uncatchable ambiguity errors -- if there are fixity errors, try the cheapFixities (which never fails) applyFixity :: [Fixity] -> Module_ -> Module_ applyFixity base modu = descendBi f modu where f x = fromMaybe (cheapFixities fixs x) $ applyFixities fixs x :: Decl_ fixs = concatMap getFixity (moduleDecls modu) ++ base -- Apply fixities, but ignoring any ambiguous fixity errors and skipping qualified names, -- local infix declarations etc. Only use as a backup, if HSE gives an error. -- -- Inspired by the code at: -- http://hackage.haskell.org/trac/haskell-prime/attachment/wiki/FixityResolution/resolve.hs cheapFixities :: [Fixity] -> Decl_ -> Decl_ cheapFixities fixs = descendBi (transform f) where ask = askFixity fixs f o@(InfixApp s1 (InfixApp s2 x op1 y) op2 z) | p1 == p2 && (a1 /= a2 || isAssocNone a1) = o -- Ambiguous infix expression! | p1 > p2 || p1 == p2 && (isAssocLeft a1 || isAssocNone a2) = o | otherwise = InfixApp s1 x op1 (f $ InfixApp s1 y op2 z) where (a1,p1) = ask op1 (a2,p2) = ask op2 f x = x askFixity :: [Fixity] -> QOp S -> (Assoc (), Int) askFixity xs = \k -> Map.findWithDefault (AssocLeft (), 9) (fromNamed k) mp where mp = Map.fromList [(s,(a,p)) | Fixity a p x <- xs, let s = fromNamed $ fmap (const an) x, s /= ""] hlint-2.0.11/src/Hint/0000755000000000000000000000000013210071537012567 5ustar0000000000000000hlint-2.0.11/src/Hint/Util.hs0000644000000000000000000001050413210071537014040 0ustar0000000000000000{-# LANGUAGE PatternGuards, ViewPatterns #-} module Hint.Util(niceLambda, simplifyExp, niceLambdaR) where import HSE.All import Data.List.Extra import Refact.Types import Refact import qualified Refact.Types as R (SrcSpan) niceLambda :: [String] -> Exp_ -> Exp_ niceLambda ss e = fst (niceLambdaR ss e) -- | Generate a lambda, but prettier (if possible). -- Generally no lambda is good, but removing just some arguments isn't so useful. niceLambdaR :: [String] -> Exp_ -> (Exp_, R.SrcSpan -> [Refactoring R.SrcSpan]) -- \xs -> (e) ==> \xs -> e niceLambdaR xs (Paren l x) = niceLambdaR xs x -- \xs -> \v vs -> e ==> \xs v -> \vs -> e -- \xs -> \ -> e ==> \xs -> e niceLambdaR xs (Lambda _ ((view -> PVar_ v):vs) x) | v `notElem` xs = niceLambdaR (xs++[v]) (Lambda an vs x) niceLambdaR xs (Lambda _ [] x) = niceLambdaR xs x -- \ -> e ==> e niceLambdaR [] x = (x, const []) -- \vs v -> e $ v ==> \vs -> e niceLambdaR (unsnoc -> Just (vs, v)) (InfixApp _ e (isDol -> True) (view -> Var_ v2)) | v == v2, vars e `disjoint` [v] = niceLambdaR vs e -- \xs -> e xs ==> e niceLambdaR xs (fromAppsWithLoc -> e) | map view xs2 == map Var_ xs, vars e2 `disjoint` xs, not $ null e2 = (apps e2, \s -> [Replace Expr s [("x", pos)] "x"]) where (e',xs') = splitAt (length e - length xs) e (e2, xs2) = (map fst e', map fst xs') pos = toRefactSrcSpan . srcInfoSpan $ snd (last e') -- \x y -> x + y ==> (+) niceLambdaR [x,y] (InfixApp _ (view -> Var_ x1) (opExp -> op) (view -> Var_ y1)) | x == x1, y == y1, vars op `disjoint` [x,y] = (op, \s -> [Replace Expr s [] (prettyPrint op)]) -- \x -> x + b ==> (+ b) [heuristic, b must be a single lexeme, or gets too complex] niceLambdaR [x] (view -> App2 (expOp -> Just op) a b) | isLexeme b, view a == Var_ x, x `notElem` vars b, allowRightSection (fromNamed op) = let e = rebracket1 $ RightSection an op b in (e, \s -> [Replace Expr s [] (prettyPrint e)]) -- \x y -> f y x = flip f niceLambdaR [x,y] (view -> App2 op (view -> Var_ y1) (view -> Var_ x1)) | x == x1, y == y1, vars op `disjoint` [x,y] = (gen op, \s -> [Replace Expr s [("x", toSS op)] (prettyPrint $ gen (toNamed "x"))]) where gen = App an (toNamed "flip") -- \x -> f (b x) ==> f . b -- \x -> f $ b x ==> f . b niceLambdaR [x] y | Just (z, subts) <- factor y, x `notElem` vars z = (z, \s -> [mkRefact subts s]) where -- factor the expression with respect to x factor y@(App _ ini lst) | view lst == Var_ x = Just (ini, [ann ini]) factor y@(App _ ini lst) | Just (z, ss) <- factor lst = let r = niceDotApp ini z in if r == z then Just (r, ss) else Just (r, ann ini : ss) factor (InfixApp _ y op (factor -> Just (z, ss))) | isDol op = let r = niceDotApp y z in if r == z then Just (r, ss) else Just (r, ann y : ss) factor (Paren _ y@App{}) = factor y factor _ = Nothing mkRefact :: [S] -> R.SrcSpan -> Refactoring R.SrcSpan mkRefact subts s = let tempSubts = zipWith (\a b -> ([a], toRefactSrcSpan $ srcInfoSpan b)) ['a' .. 'z'] subts template = dotApps (map (toNamed . fst) tempSubts) in Replace Expr s tempSubts (prettyPrint template) -- \x -> (x +) ==> (+) -- Section handling is not yet supported for refactoring niceLambdaR [x] (LeftSection _ (view -> Var_ x1) op) | x == x1 = let e = opExp op in (e, \s -> [Replace Expr s [] (prettyPrint e)]) -- base case niceLambdaR ps x = (Lambda an (map toNamed ps) x, const []) -- ($) . b ==> b niceDotApp :: Exp_ -> Exp_ -> Exp_ niceDotApp a b | a ~= "$" = b | otherwise = dotApp a b -- | Convert expressions which have redundant junk in them away. -- Mainly so that later stages can match on fewer alternatives. simplifyExp :: Exp_ -> Exp_ simplifyExp (InfixApp _ x dol y) | isDol dol = App an x (paren y) simplifyExp (Let _ (BDecls _ [PatBind _ (view -> PVar_ x) (UnGuardedRhs _ y) Nothing]) z) | x `notElem` vars y && length [() | UnQual _ a <- universeS z, prettyPrint a == x] <= 1 = transform f z where f (view -> Var_ x') | x == x' = paren y f x = x simplifyExp x = x hlint-2.0.11/src/Hint/Unsafe.hs0000644000000000000000000000374513210071537014355 0ustar0000000000000000 {- Find things that are unsafe {-# NOINLINE slaves #-}; slaves = unsafePerformIO newIO slaves = unsafePerformIO Multimap.newIO -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO Multimap.newIO slaves = unsafePerformIO $ f y where foo = 1 -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO $ f y where foo = 1 slaves v = unsafePerformIO $ Multimap.newIO where foo = 1 slaves v = x where x = unsafePerformIO $ Multimap.newIO slaves = x where x = unsafePerformIO $ Multimap.newIO -- {-# NOINLINE slaves #-} ; slaves = x where x = unsafePerformIO $ Multimap.newIO slaves = unsafePerformIO . bar slaves = unsafePerformIO . baz $ x -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO . baz $ x slaves = unsafePerformIO . baz $ x -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO . baz $ x -} module Hint.Unsafe(unsafeHint) where import Hint.Type import Data.Char import Refact.Types unsafeHint :: ModuHint unsafeHint _ m = [ rawIdea Warning "Missing NOINLINE pragma" (srcInfoSpan $ ann d) (prettyPrint d) (Just $ dropWhile isSpace (prettyPrint $ gen x) ++ "\n" ++ prettyPrint d) [] [InsertComment (toSS d) (prettyPrint $ gen x)] | d@(PatBind _ (PVar _ x) _ _) <- moduleDecls m , isUnsafeDecl d, x `notElem_` noinline] where gen x = InlineSig an False Nothing $ UnQual an x noinline = [q | InlineSig _ False Nothing (UnQual _ q) <- moduleDecls m] isUnsafeDecl :: Decl_ -> Bool isUnsafeDecl (PatBind _ _ rhs bind) = any isUnsafeApp (childrenBi rhs) || any isUnsafeDecl (childrenBi bind) isUnsafeDecl _ = False -- Am I equivalent to @unsafePerformIO x@ isUnsafeApp :: Exp_ -> Bool isUnsafeApp (InfixApp _ x d _) | isDol d = isUnsafeFun x isUnsafeApp (App _ x _) = isUnsafeFun x isUnsafeApp _ = False -- Am I equivalent to @unsafePerformIO . x@ isUnsafeFun :: Exp_ -> Bool isUnsafeFun (Var _ x) | x ~= "unsafePerformIO" = True isUnsafeFun (InfixApp _ x d _) | isDot d = isUnsafeFun x isUnsafeFun _ = False hlint-2.0.11/src/Hint/Type.hs0000644000000000000000000000271513210071537014051 0ustar0000000000000000 module Hint.Type( DeclHint, ModuHint, CrossHint, Hint(..), module Export ) where import Data.Monoid import Config.Type import HSE.All as Export import Idea as Export import Prelude import Refact as Export type DeclHint = Scope -> Module_ -> Decl_ -> [Idea] type ModuHint = Scope -> Module_ -> [Idea] type CrossHint = [(Scope, Module_)] -> [Idea] -- | Functions to generate hints, combined using the 'Monoid' instance. data Hint {- PUBLIC -} = Hint {hintModules :: [Setting] -> [(Scope, Module SrcSpanInfo)] -> [Idea] -- ^ Given a list of modules (and their scope information) generate some 'Idea's. ,hintModule :: [Setting] -> Scope -> Module SrcSpanInfo -> [Idea] -- ^ Given a single module and its scope information generate some 'Idea's. ,hintDecl :: [Setting] -> Scope -> Module SrcSpanInfo -> Decl SrcSpanInfo -> [Idea] -- ^ Given a declaration (with a module and scope) generate some 'Idea's. -- This function will be partially applied with one module/scope, then used on multiple 'Decl' values. ,hintComment :: [Setting] -> Comment -> [Idea] -- ^ Given a comment generate some 'Idea's. } instance Monoid Hint where mempty = Hint (\_ _ -> []) (\_ _ _ -> []) (\_ _ _ _ -> []) (\_ _ -> []) mappend (Hint x1 x2 x3 x4) (Hint y1 y2 y3 y4) = Hint (\a b -> x1 a b ++ y1 a b) (\a b c -> x2 a b c ++ y2 a b c) (\a b c d -> x3 a b c d ++ y3 a b c d) (\a b -> x4 a b ++ y4 a b) hlint-2.0.11/src/Hint/Restrict.hs0000644000000000000000000000750213210071537014726 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} module Hint.Restrict(restrictHint) where {- -- These tests rely on the .hlint.yaml file in the root foo = unsafePerformIO -- module Util where otherFunc = unsafePerformIO $ print 1 -- module Util where exitMessageImpure = unsafePerformIO $ print 1 foo = unsafePerformOI -} import qualified Data.Map as Map import Config.Type import Hint.Type import Data.List import Data.Maybe import Data.Monoid import Control.Applicative import Prelude -- FIXME: The settings should be partially applied, but that's hard to orchestrate right now restrictHint :: [Setting] -> ModuHint restrictHint settings scope m = checkPragmas modu (modulePragmas m) restrict ++ maybe [] (checkImports modu $ moduleImports m) (Map.lookup RestrictModule restrict) ++ maybe [] (checkFunctions modu $ moduleDecls m) (Map.lookup RestrictFunction restrict) where modu = moduleName m restrict = restrictions settings --------------------------------------------------------------------- -- UTILITIES data RestrictItem = RestrictItem {riAs :: [String] ,riWithin :: [(String, String)] } instance Monoid RestrictItem where mempty = RestrictItem [] [] mappend (RestrictItem x1 x2) (RestrictItem y1 y2) = RestrictItem (x1<>y1) (x2<>y2) restrictions :: [Setting] -> Map.Map RestrictType (Bool, Map.Map String RestrictItem) restrictions settings = Map.map f $ Map.fromListWith (++) [(restrictType x, [x]) | SettingRestrict x <- settings] where f rs = (all restrictDefault rs ,Map.fromListWith (<>) [(s, RestrictItem restrictAs restrictWithin) | Restrict{..} <- rs, s <- restrictName]) ideaMayBreak w = w{ideaNote=[Note "may break the code"]} ideaNoTo w = w{ideaTo=Nothing} within :: String -> String -> RestrictItem -> Bool within modu func RestrictItem{..} = any (\(a,b) -> (a == modu || a == "") && (b == func || b == "")) riWithin --------------------------------------------------------------------- -- CHECKS checkPragmas :: String -> [ModulePragma S] -> Map.Map RestrictType (Bool, Map.Map String RestrictItem) -> [Idea] checkPragmas modu xs mps = f RestrictFlag "flags" onFlags ++ f RestrictExtension "extensions" onExtensions where f tag name sel = [ (if null good then ideaNoTo else id) $ ideaMayBreak $ warn ("Avoid restricted " ++ name) o (regen good) [] | Just mp <- [Map.lookup tag mps] , o <- xs, Just (xs, regen) <- [sel o] , let (good, bad) = partition (isGood mp) xs, not $ null bad] onFlags (OptionsPragma s t x) = Just (words x, OptionsPragma s t . unwords) onFlags _ = Nothing onExtensions (LanguagePragma s xs) = Just (map fromNamed xs, LanguagePragma (s :: S) . map toNamed) onExtensions _ = Nothing isGood (def, mp) x = maybe def (within modu "") $ Map.lookup x mp checkImports :: String -> [ImportDecl S] -> (Bool, Map.Map String RestrictItem) -> [Idea] checkImports modu imp (def, mp) = [ ideaMayBreak $ if not allowImport then ideaNoTo $ warn "Avoid restricted module" i i [] else warn "Avoid restricted qualification" i i{importAs=ModuleName an <$> listToMaybe riAs} [] | i@ImportDecl{..} <- imp , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def]) (fromModuleName importModule) mp , let allowImport = within modu "" ri , let allowQual = maybe True (\x -> null riAs || fromModuleName x `elem` riAs) importAs , not allowImport || not allowQual ] checkFunctions :: String -> [Decl_] -> (Bool, Map.Map String RestrictItem) -> [Idea] checkFunctions modu decls (def, mp) = [ ideaMayBreak $ ideaNoTo $ warn "Avoid restricted function" x x [] | d <- decls , let dname = fromNamed d , x@Var{} <- universeBi d , not $ maybe def (within modu dname) $ Map.lookup (fromNamed x) mp ] hlint-2.0.11/src/Hint/Pragma.hs0000644000000000000000000001006113210071537014330 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE FlexibleContexts #-} {- Suggest better pragmas OPTIONS_GHC -cpp => LANGUAGE CPP OPTIONS_GHC -fglasgow-exts => LANGUAGE ... (in HSE) OPTIONS_GHC -XFoo => LANGUAGE Foo LANGUAGE A, A => LANGUAGE A -- do not do LANGUAGE A, LANGUAGE B to combine {-# OPTIONS_GHC -cpp #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS -cpp #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_YHC -cpp #-} {-# OPTIONS_GHC -XFoo #-} -- {-# LANGUAGE Foo #-} {-# OPTIONS_GHC -fglasgow-exts #-} -- ??? {-# LANGUAGE A, B, C, A #-} -- {-# LANGUAGE A, B, C #-} {-# LANGUAGE A #-} {-# OPTIONS_GHC -cpp -foo #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -foo #-} {-# OPTIONS_GHC -cpp #-} \ {-# LANGUAGE CPP, Text #-} -- {-# LANGUAGE A #-} \ {-# LANGUAGE B #-} {-# LANGUAGE A #-} \ {-# LANGUAGE B, A #-} -- {-# LANGUAGE A, B #-} -} module Hint.Pragma(pragmaHint) where import Hint.Type import Data.List import Data.Maybe import Refact.Types import qualified Refact.Types as R pragmaHint :: ModuHint pragmaHint _ x = languageDupes lang ++ optToPragma x lang where lang = [x | x@LanguagePragma{} <- modulePragmas x] optToPragma :: Module_ -> [ModulePragma S] -> [Idea] optToPragma x lang = [pragmaIdea (OptionsToComment old ys rs) | old /= []] where (old,new,ns, rs) = unzip4 [(old,new,ns, r) | old <- modulePragmas x, Just (new,ns) <- [optToLanguage old ls] , let r = mkRefact old new ns] ls = concat [map fromNamed n | LanguagePragma _ n <- lang] ns2 = nub (concat ns) \\ ls ys = [LanguagePragma an (map toNamed ns2) | ns2 /= []] ++ catMaybes new mkRefact :: ModulePragma S -> Maybe (ModulePragma S) -> [String] -> Refactoring R.SrcSpan mkRefact old (maybe "" prettyPrint -> new) ns = let ns' = map (\n -> prettyPrint $ LanguagePragma an [toNamed n]) ns in ModifyComment (toSS old) (intercalate "\n" (filter (not . null) (new: ns'))) data PragmaIdea = SingleComment (ModulePragma S) (ModulePragma S) | MultiComment (ModulePragma S) (ModulePragma S) (ModulePragma S) | OptionsToComment [ModulePragma S] [ModulePragma S] [Refactoring R.SrcSpan] pragmaIdea :: PragmaIdea -> Idea pragmaIdea pidea = case pidea of SingleComment old new -> mkIdea (srcInfoSpan . ann $ old) (prettyPrint old) (Just $ prettyPrint new) [] [ModifyComment (toSS old) (prettyPrint new)] MultiComment repl delete new -> mkIdea (srcInfoSpan . ann $ repl) (f [repl, delete]) (Just $ prettyPrint new) [] [ ModifyComment (toSS repl) (prettyPrint new) , ModifyComment (toSS delete) ""] OptionsToComment old new r -> mkIdea (srcInfoSpan . ann . head $ old) (f old) (Just $ f new) [] r where f = unlines . map prettyPrint mkIdea = rawIdea Warning "Use better pragmas" languageDupes :: [ModulePragma S] -> [Idea] languageDupes (a@(LanguagePragma _ x):xs) = (if nub_ x `neqList` x then [pragmaIdea (SingleComment a (LanguagePragma (ann a) $ nub_ x))] else [pragmaIdea (MultiComment a b (LanguagePragma (ann a) (nub_ $ x ++ y))) | b@(LanguagePragma _ y) <- xs, not $ null $ intersect_ x y]) ++ languageDupes xs languageDupes _ = [] -- Given a pragma, can you extract some language features out strToLanguage :: String -> Maybe [String] strToLanguage "-cpp" = Just ["CPP"] strToLanguage x | "-X" `isPrefixOf` x = Just [drop 2 x] strToLanguage "-fglasgow-exts" = Just $ map prettyExtension glasgowExts strToLanguage _ = Nothing optToLanguage :: ModulePragma S -> [String] -> Maybe (Maybe (ModulePragma S), [String]) optToLanguage (OptionsPragma sl tool val) ls | maybe True (== GHC) tool && any isJust vs = Just (res, filter (not . (`elem` ls)) (concat $ catMaybes vs)) where strs = words val vs = map strToLanguage strs keep = concat $ zipWith (\v s -> [s | isNothing v]) vs strs res = if null keep then Nothing else Just $ OptionsPragma sl tool (unwords keep) optToLanguage _ _ = Nothing hlint-2.0.11/src/Hint/Pattern.hs0000644000000000000000000002036013210071537014541 0ustar0000000000000000{-# LANGUAGE ViewPatterns, PatternGuards #-} {- Improve the structure of code yes x y = if a then b else if c then d else e -- yes x y ; | a = b ; | c = d ; | otherwise = e x `yes` y = if a then b else if c then d else e -- x `yes` y ; | a = b ; | c = d ; | otherwise = e no x y = if a then b else c -- foo b | c <- f b = c -- foo (f -> c) = c -- foo x y b z | c:cs <- f g b = c -- foo x y (f g -> c:cs) z = c foo b | c <- f b = c + b foo b | c <- f b = c where f = here foo b | c <- f b = c where foo = b foo b | c <- f b = c \ | c <- f b = c foo x = yes x x where yes x y = if a then b else if c then d else e -- yes x y ; | a = b ; | c = d ; | otherwise = e foo x | otherwise = y -- foo x = y foo x = x + x where -- foo x = x + x foo x | a = b | True = d -- foo x | a = b ; | otherwise = d foo (Bar _ _ _ _) = x -- Bar{} foo (Bar _ x _ _) = x foo (Bar _ _) = x foo = case f v of _ -> x -- x foo = case v of v -> x -- x foo = case v of z -> z foo = case v of _ | False -> x foo = case v of !True -> x -- True foo = case v of !(Just x) -> x -- (Just x) foo = case v of !(x : xs) -> x -- (x:xs) foo = case v of !1 -> x -- 1 foo = case v of !x -> x foo = case v of !(I# x) -> y -- (I# x) foo = let ~x = 1 in y -- x foo = let ~(x:xs) = y in z foo = let !x = undefined in y foo = let !(I# x) = 4 in x foo = let !(Just x) = Nothing in 3 foo = 1 where f !False = 2 -- False foo = 1 where !False = True foo = 1 where g (Just !True) = Nothing -- True foo = 1 where Just !True = Nothing foo otherwise = 1 -- _ foo ~x = y -- x {-# LANGUAGE Strict #-} foo ~x = y foo !(x, y) = x -- (x, y) foo ![x] = x -- [x] foo !Bar { bar = x } = x -- Bar { bar = x } l !(() :: ()) = x -- (() :: ()) -} module Hint.Pattern(patternHint) where import Hint.Type import Data.Function import Data.List.Extra import Data.Tuple import Data.Maybe import Data.Either import Refact.Types hiding (RType(Pattern, Match)) import qualified Refact.Types as R (RType(Pattern, Match), SrcSpan) patternHint :: DeclHint patternHint _ modu x = concatMap (uncurry hints . swap) (asPattern x) ++ -- PatBind (used in Let and Where) contains lazy-by-default patterns, everything else is strict concatMap (patHint strict False) (universeBi [p | PatBind _ p _ _ <- universe x]) ++ concatMap (patHint strict True) (universeBi $ transform noPatBind x) ++ concatMap expHint (universeBi x) where noPatBind (PatBind a _ b c) = PatBind a (PWildCard a) b c noPatBind x = x strict = "Strict" `elem` [n | LanguagePragma _ ns <- modulePragmas modu, Ident _ n <- ns] hints :: (String -> Pattern -> [Refactoring R.SrcSpan] -> Idea) -> Pattern -> [Idea] hints gen (Pattern l rtype pat (UnGuardedRhs d bod) bind) | length guards > 2 = [gen "Use guards" (Pattern l rtype pat (GuardedRhss d guards) bind) [refactoring]] where rawGuards = asGuards bod mkGuard a = GuardedRhs an [Qualifier an a] guards = map (uncurry mkGuard) rawGuards (lhs, rhs) = unzip rawGuards mkTemplate c ps = -- Check if the expression has been injected or is natural let checkAn p v = if ann p == an then Left p else Right ( c ++ [v], toSS p) in zipWith checkAn ps ['1' .. '9'] patSubts = case pat of [p] -> [Left p] -- Substitution doesn't work properly for PatBinds -- This will probably produce -- unexpected results if the pattern -- contains any template variables ps -> mkTemplate "p100" ps guardSubts = mkTemplate "g100" lhs exprSubts = mkTemplate "e100" rhs templateGuards = zipWith (mkGuard `on` toString) guardSubts exprSubts toString (Left e) = e toString (Right (v, _)) = toNamed v template = fromMaybe "" $ ideaTo (gen "" (Pattern l rtype (map toString patSubts) (GuardedRhss d templateGuards) bind) []) f :: [Either a (String, R.SrcSpan)] -> [(String, R.SrcSpan)] f = rights refactoring = Replace rtype (toRefactSrcSpan $ srcInfoSpan l) (f patSubts ++ f guardSubts ++ f exprSubts) template {- -- Do not suggest view patterns, they aren't something everyone likes sufficiently hints gen (Pattern pats (GuardedRhss _ [GuardedRhs _ [Generator _ pat (App _ op (view -> Var_ p))] bod]) bind) | Just i <- findIndex (=~= (toNamed p :: Pat_)) pats , p `notElem` (vars bod ++ vars bind) , vars op `disjoint` decsBind, pvars pats `disjoint` vars op, pvars pat `disjoint` pvars pats = [gen "Use view patterns" $ Pattern (take i pats ++ [PParen an $ PViewPat an op pat] ++ drop (i+1) pats) (UnGuardedRhs an bod) bind] where decsBind = nub $ concatMap declBind $ childrenBi bind -} hints gen (Pattern l t pats (GuardedRhss _ [GuardedRhs _ [test] bod]) bind) | prettyPrint test `elem` ["otherwise","True"] = [gen "Redundant guard" (Pattern l t pats (UnGuardedRhs an bod) bind) [Delete Stmt (toSS test)]] hints gen (Pattern l t pats bod (Just bind)) | f bind = [gen "Redundant where" (Pattern l t pats bod Nothing) []] where f (BDecls _ x) = null x f (IPBinds _ x) = null x hints gen (Pattern l t pats (GuardedRhss _ (unsnoc -> Just (gs, GuardedRhs _ [test] bod))) bind) | prettyPrint test == "True" = [gen "Use otherwise" (Pattern l t pats (GuardedRhss an $ gs ++ [GuardedRhs an [Qualifier an $ toNamed "otherwise"] bod]) bind) [Replace Expr (toSS test) [] "otherwise"]] hints _ _ = [] asGuards :: Exp_ -> [(Exp S, Exp S)] asGuards (Paren _ x) = asGuards x asGuards (If _ a b c) = (a, b) : asGuards c asGuards x = [(toNamed "otherwise", x)] data Pattern = Pattern S R.RType [Pat_] (Rhs S) (Maybe (Binds S)) -- Invariant: Number of patterns may not change asPattern :: Decl_ -> [(Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea)] asPattern x = concatMap decl (universeBi x) ++ concatMap alt (universeBi x) where decl o@(PatBind a pat rhs bind) = [(Pattern a Bind [pat] rhs bind, \msg (Pattern _ _ [pat] rhs bind) rs -> suggest msg o (PatBind a pat rhs bind) rs)] decl (FunBind _ xs) = map match xs decl _ = [] match o@(Match a b pat rhs bind) = (Pattern a R.Match pat rhs bind, \msg (Pattern _ _ pat rhs bind) rs -> suggest msg o (Match a b pat rhs bind) rs) match o@(InfixMatch a p b ps rhs bind) = (Pattern a R.Match (p:ps) rhs bind, \msg (Pattern _ _ (p:ps) rhs bind) rs -> suggest msg o (InfixMatch a p b ps rhs bind) rs) alt o@(Alt a pat rhs bind) = [(Pattern a R.Match [pat] rhs bind, \msg (Pattern _ _ [pat] rhs bind) rs -> suggest msg o (Alt a pat rhs bind) [])] -- First Bool is if Strict is a language extension -- Second Bool is if this pattern in this context is going to be evaluated strictly patHint :: Bool -> Bool -> Pat_ -> [Idea] patHint lang strict o@(PApp _ name args) | length args >= 3 && all isPWildCard args = [suggest "Use record patterns" o (PRec an name []) [Replace R.Pattern (toSS o) [] (prettyPrint $ PRec an name [])] ] patHint lang strict o@(PVar _ v) | prettyPrint v == "otherwise" = [warn "Used otherwise as a pattern" o (PWildCard an) []] patHint lang strict o@(PBangPat _ x) | strict, f x = [warn "Redundant bang pattern" o x [r]] where f (PParen _ x) = f x f (PAsPat _ _ x) = f x f PLit{} = True f PApp{} = True f PInfixApp{} = True f PTuple{} = True f PList{} = True f PRec{} = True f (PatTypeSig _ x _) = f x f _ = False r = Replace R.Pattern (toSS o) [("x", toSS x)] "x" patHint False strict o@(PIrrPat _ x) | f x = [warn "Redundant irrefutable pattern" o x [r]] where f (PParen _ x) = f x f (PAsPat _ _ x) = f x f PWildCard{} = True f PVar{} = True f _ = False r = Replace R.Pattern (toSS o) [("x", toSS x)] "x" patHint _ _ _ = [] expHint :: Exp_ -> [Idea] expHint o@(Case _ _ [Alt _ PWildCard{} (UnGuardedRhs _ e) Nothing]) = [suggest "Redundant case" o e [r]] where r = Replace Expr (toSS o) [("x", toSS e)] "x" expHint o@(Case _ (Var _ x) [Alt _ (PVar _ y) (UnGuardedRhs _ e) Nothing]) | x =~= UnQual an y = [suggest "Redundant case" o e [r]] where r = Replace Expr (toSS o) [("x", toSS e)] "x" expHint _ = [] hlint-2.0.11/src/Hint/NewType.hs0000644000000000000000000000325013210071537014516 0ustar0000000000000000{- Suggest newtype instead of data for type declarations that have only one field. Don't suggest newtype for existentially quantified data types because it is not valid. data Foo = Foo Int -- newtype Foo = Foo Int data Foo = Foo Int deriving (Show, Eq) -- newtype Foo = Foo Int deriving (Show, Eq) data Foo = Foo { field :: Int } deriving Show -- newtype Foo = Foo { field :: Int } deriving Show data Foo a b = Foo a -- newtype Foo a b = Foo a data Foo = Foo { field1, field2 :: Int} data S a = forall b . Show b => S b data Color a = Red a | Green a | Blue a data Pair a b = Pair a b data Foo = Bar data X = Y {-# UNPACK #-} !Int -- newtype X = Y Int data A = A {b :: !C} -- newtype A = A {b :: C} data A = A Int# data A = A () -- newtype A = A () -} module Hint.NewType (newtypeHint) where import Hint.Type newtypeHint :: DeclHint newtypeHint _ _ = newtypeHintDecl newtypeHintDecl :: Decl_ -> [Idea] newtypeHintDecl x | Just (DataType s, t, f) <- singleSimpleField x = [(suggestN "Use newtype instead of data" x $ f (NewType s) $ fromTyBang t) {ideaNote = [DecreasesLaziness | not $ isTyBang t]}] newtypeHintDecl _ = [] singleSimpleField :: Decl_ -> Maybe (DataOrNew S, Type_, DataOrNew S -> Type_ -> Decl_) singleSimpleField (DataDecl x1 dt x2 x3 [QualConDecl y1 Nothing y2 ctor] x4) | Just (t, ft) <- f ctor = Just (dt, t, \dt t -> DataDecl x1 dt x2 x3 [QualConDecl y1 Nothing y2 $ ft t] x4) where f (ConDecl x1 x2 [t]) | not $ isKindHash t = Just (t, \t -> ConDecl x1 x2 [t]) f (RecDecl x1 x2 [FieldDecl y1 [y2] t]) = Just (t, \t -> RecDecl x1 x2 [FieldDecl y1 [y2] t]) f _ = Nothing singleSimpleField _ = Nothing hlint-2.0.11/src/Hint/Naming.hs0000644000000000000000000000631213210071537014336 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {- Suggest the use of camelCase Only permit: _*[A-Za-z]*_*#*'* Apply this to things that would get exported by default only Also allow prop_ as it's a standard QuickCheck idiom Also allow case_ as it's a standard test-framework-th idiom Also allow test_ as it's a standard tasty-th idiom Also allow numbers separated by _ Also don't suggest anything mentioned elsewhere in the module data Yes = Foo | Bar'Test -- data Yes = Foo | BarTest data Yes = Bar | Test_Bar -- data Yes = Bar | TestBar data No = a :::: b data Yes = Foo {bar_cap :: Int} -- data Yes = Foo{barCap :: Int} data No = FOO | BarBAR | BarBBar yes_foo = yes_foo + yes_foo -- yesFoo = ... no = 1 where yes_foo = 2 a -== b = 1 myTest = 1; my_test = 1 semiring'laws = 1 -- semiringLaws = ... data Yes = FOO_A | Foo_B -- data Yes = FOO_A | FooB case_foo = 1 test_foo = 1 cast_foo = 1 -- castFoo = ... replicateM_ = 1 _foo__ = 1 section_1_1 = 1 runMutator# = 1 -} module Hint.Naming(namingHint) where import Hint.Type import Data.List import Data.Data import Data.Char import Data.Maybe import qualified Data.Set as Set namingHint :: DeclHint namingHint _ modu = naming $ Set.fromList [x | Ident _ x <- universeS modu] naming :: Set.Set String -> Decl_ -> [Idea] naming seen x = [suggestN "Use camelCase" x2 (replaceNames res x2) | not $ null res] where res = [(n,y) | n <- nub $ getNames x, Just y <- [suggestName n], not $ y `Set.member` seen] x2 = shorten x shorten :: Decl_ -> Decl_ shorten x = case x of FunBind sl (Match a b c d _:_) -> FunBind sl [f (Match a b c) d] PatBind a b c _ -> f (PatBind a b) c x -> x where dots = Var an ellipses f cont (UnGuardedRhs _ _) = cont (UnGuardedRhs an dots) Nothing f cont (GuardedRhss _ _) = cont (GuardedRhss an [GuardedRhs an [Qualifier an dots] dots]) Nothing getNames :: Decl_ -> [String] getNames x = case x of FunBind{} -> name PatBind{} -> name TypeDecl{} -> name DataDecl _ _ _ _ cons _ -> name ++ [fromNamed x | QualConDecl _ _ _ x <- cons, x <- f x] GDataDecl _ _ _ _ _ cons _ -> name ++ [fromNamed x | GadtDecl _ x _ _ <- cons] TypeFamDecl{} -> name DataFamDecl{} -> name ClassDecl{} -> name _ -> [] where name = [fromNamed x] f (ConDecl _ x _) = [x] f (InfixConDecl _ _ x _) = [x] f (RecDecl _ x ys) = x : concat [y | FieldDecl _ y _ <- ys] suggestName :: String -> Maybe String suggestName x | isSym x || good || not (any isLower x) || any isDigit x || any (`isPrefixOf` x) ["prop_","case_","test_"] = Nothing | otherwise = Just $ f x where good = all isAlphaNum $ drp '_' $ drp '#' $ drp '\'' $ reverse $ drp '_' x drp x = dropWhile (== x) f xs = us ++ g ys where (us,ys) = span (== '_') xs g x | x `elem` ["_","'","_'"] = x g (a:x:xs) | a `elem` "_'" && isAlphaNum x = toUpper x : g xs g (x:xs) | isAlphaNum x = x : g xs | otherwise = g xs g [] = [] replaceNames :: Data a => [(String,String)] -> a -> a replaceNames rep = descendBi f where f (Ident _ x) = Ident an $ fromMaybe x $ lookup x rep f x = x hlint-2.0.11/src/Hint/Monad.hs0000644000000000000000000001334113210071537014163 0ustar0000000000000000{-# LANGUAGE ViewPatterns, PatternGuards, FlexibleContexts #-} {- Find and match: mapM, foldM, forM, replicateM, sequence, zipWithM not at the last line of a do statement, or to the left of >> Use let x = y instead of x <- return y, unless x is contained within y, or bound more than once in that do block. yes = do mapM print a; return b -- mapM_ print a yes = do _ <- mapM print a; return b -- mapM_ print a no = mapM print a no = do foo ; mapM print a yes = do (bar+foo) -- (bar+foo) no = do bar ; foo yes = do bar; a <- foo; return a -- do bar; foo no = do bar; a <- foo; return b yes = do x <- bar; x -- do join bar no = do x <- bar; x; x {-# LANGUAGE RecursiveDo #-}; no = mdo hook <- mkTrigger pat (act >> rmHook hook) ; return hook yes = do x <- return y; foo x -- @Suggestion do let x = y; foo x yes = do x <- return $ y + z; foo x -- do let x = y + z; foo x no = do x <- return x; foo x no = do x <- return y; x <- return y; foo x yes = do forM files $ \x -> return (); return () -- forM_ files $ \x -> return () yes = do if a then forM x y else sequence z q; return () -- if a then forM_ x y else sequence_ z q yes = do case a of {_ -> forM x y; x:xs -> forM x xs}; return () -- case a of _ -> forM_ x y ; x:xs -> forM_ x xs foldM_ f a xs = foldM f a xs >> return () folder f a xs = foldM f a xs >> return () -- foldM_ f a xs folder f a xs = foldM f a xs >>= \_ -> return () -- foldM_ f a xs yes = mapM async ds >>= mapM wait >> return () -- mapM async ds >>= mapM_ wait main = "wait" ~> do f a $ sleep 10 main = f $ do g a $ sleep 10 -- g a $ sleep 10 main = do f a $ sleep 10 -- f a $ sleep 10 -} module Hint.Monad(monadHint) where import Control.Applicative import Data.Tuple.Extra import Data.Maybe import Data.List import Hint.Type import Refact.Types import qualified Refact.Types as R import Prelude badFuncs = ["mapM","foldM","forM","replicateM","sequence","zipWithM"] monadHint :: DeclHint monadHint _ _ d = concatMap (monadExp d) $ universeParentExp d monadExp :: Decl_ -> (Maybe (Int, Exp_), Exp_) -> [Idea] monadExp decl (parent, x) = case x of (view -> App2 op x1 x2) | op ~= ">>" -> f x1 (view -> App2 op x1 (view -> LamConst1 _)) | op ~= ">>=" -> f x1 Do _ xs -> [warn "Redundant return" x (Do an y) rs | Just (y, rs) <- [monadReturn xs]] ++ [warn "Use join" x (Do an y) rs | Just (y, rs) <- [monadJoin xs ['a'..'z']]] ++ [warn "Redundant do" x y [Replace Expr (toSS x) [("y", toSS y)] "y"] | [Qualifier _ y] <- [xs], not $ doOperator parent y] ++ [suggest "Use let" x (Do an y) rs | Just (y, rs) <- [monadLet xs]] ++ concat [f x | Qualifier _ x <- init xs] ++ concat [f x | Generator _ (PWildCard _) x <- init xs] _ -> [] where f x = [warn ("Use " ++ name) x y r | Just (name,y, r) <- [monadCall x], fromNamed decl /= name] -- Sometimes people write a * do a + b, to avoid brackets doOperator (Just (1, InfixApp _ _ op _)) InfixApp{} | not $ isDol op = True doOperator _ _ = False middle :: (b -> d) -> (a, b, c) -> (a, d, c) middle f (a,b,c) = (a, f b, c) -- see through Paren and down if/case etc -- return the name to use in the hint, and the revised expression monadCall :: Exp_ -> Maybe (String,Exp_, [Refactoring R.SrcSpan]) monadCall (Paren l x) = middle (Paren l) <$> monadCall x monadCall (App l x y) = middle (\x -> App l x y) <$> monadCall x monadCall (InfixApp l x op y) | isDol op = middle (\x -> InfixApp l x op y) <$> monadCall x | op ~= ">>=" = middle (InfixApp l x op) <$> monadCall y monadCall (replaceBranches -> (bs@(_:_), gen)) | all isJust res = Just ("Use simple functions", gen $ map (\(Just (a,b,c)) -> b) res, rs) where res = map monadCall bs rs = concatMap (\(Just (a,b,c)) -> c) res monadCall x | x2:_ <- filter (x ~=) badFuncs = let x3 = x2 ++ "_" in Just (x3, toNamed x3, [Replace Expr (toSS x) [] x3]) monadCall _ = Nothing monadReturn :: [Stmt S] -> Maybe ([Stmt S], [Refactoring R.SrcSpan]) monadReturn (reverse -> q@(Qualifier _ (App _ ret (Var _ v))):g@(Generator _ (PVar _ p) x):rest) | ret ~= "return", fromNamed v == fromNamed p = Just (reverse (Qualifier an x : rest), [Replace Stmt (toSS g) [("x", toSS x)] "x", Delete Stmt (toSS q)]) monadReturn _ = Nothing monadJoin :: [Stmt S] -> String -> Maybe ([Stmt S], [Refactoring R.SrcSpan]) monadJoin (g@(Generator _ (view -> PVar_ p) x):q@(Qualifier _ (view -> Var_ v)):xs) (c:cs) | p == v && v `notElem` varss xs = Just . f $ fromMaybe def (monadJoin xs cs) where gen expr = Qualifier (ann x) (rebracket1 $ App an (toNamed "join") expr) def = (xs, []) f (ss, rs) = (s:ss, r ++ rs) s = gen x r = [Replace Stmt (toSS g) [("x", toSS x)] "join x", Delete Stmt (toSS q)] monadJoin (x:xs) cs = first (x:) <$> monadJoin xs cs monadJoin [] _ = Nothing monadLet :: [Stmt S] -> Maybe ([Stmt S], [Refactoring R.SrcSpan]) monadLet xs = if null rs then Nothing else Just (ys, rs) where (ys, catMaybes -> rs) = unzip $ map mkLet xs vs = concatMap pvars [p | Generator _ p _ <- xs] mkLet g@(Generator _ v@(view -> PVar_ p) (fromRet -> Just y)) | p `notElem` vars y, p `notElem` delete p vs = (template (toNamed p) y, Just refact) where refact = Replace Stmt (toSS g) [("lhs", toSS v), ("rhs", toSS y)] (prettyPrint $ template (toNamed "lhs") (toNamed "rhs")) mkLet x = (x, Nothing) template lhs rhs = LetStmt an $ BDecls an [PatBind an lhs (UnGuardedRhs an rhs) Nothing] fromRet (Paren _ x) = fromRet x fromRet (InfixApp _ x y z) | opExp y ~= "$" = fromRet $ App an x z fromRet (App _ x y) | x ~= "return" = Just y fromRet _ = Nothing hlint-2.0.11/src/Hint/Match.hs0000644000000000000000000001657613210071537014176 0ustar0000000000000000{-# LANGUAGE PatternGuards, ViewPatterns, RecordWildCards, FlexibleContexts, ScopedTypeVariables #-} {- The matching does a fairly simple unification between the two terms, treating any single letter variable on the left as a free variable. After the matching we substitute, transform and check the side conditions. We also "see through" both ($) and (.) functions on the right. TRANSFORM PATTERNS _eval_ - perform deep evaluation, must be used at the top of a RHS _noParen_ - don't bracket this particular item SIDE CONDITIONS (&&), (||), not - boolean connectives isAtom x - does x never need brackets isFoo x - is the root constructor of x a "Foo" notEq x y - are x and y not equal notIn xs ys - are all x variables not in ys expressions noTypeCheck, noQuickCheck - no semantics, a hint for testing only ($) AND (.) We see through ($)/(.) by expanding it if nothing else matches. We also see through (.) by translating rules that have (.) equivalents to separate rules. For example: concat (map f x) ==> concatMap f x -- we spot both these rules can eta reduce with respect to x concat . map f ==> concatMap f -- we use the associativity of (.) to add concat . map f . x ==> concatMap f . x -- currently 36 of 169 rules have (.) equivalents We see through (.) if the RHS is dull using id, e.g. not (not x) ==> x not . not ==> id not . not . x ==> x -} module Hint.Match(readMatch) where import Control.Applicative import Data.List.Extra import Data.Maybe import Config.Type import Hint.Type import Control.Monad import Data.Tuple.Extra import HSE.Unify import qualified Data.Set as Set import Prelude import qualified Refact.Types as R fmapAn = fmap (const an) --------------------------------------------------------------------- -- READ THE RULE readMatch :: [HintRule] -> DeclHint readMatch settings = findIdeas (concatMap readRule settings) readRule :: HintRule -> [HintRule] readRule (m@HintRule{hintRuleLHS=(fmapAn -> hintRuleLHS), hintRuleRHS=(fmapAn -> hintRuleRHS), hintRuleSide=(fmap fmapAn -> hintRuleSide)}) = (:) m{hintRuleLHS=hintRuleLHS,hintRuleSide=hintRuleSide,hintRuleRHS=hintRuleRHS} $ do (l,v1) <- dotVersion hintRuleLHS (r,v2) <- dotVersion hintRuleRHS guard $ v1 == v2 && l /= [] && (length l > 1 || length r > 1) && Set.notMember v1 (freeVars $ maybeToList hintRuleSide ++ l ++ r) if r /= [] then [m{hintRuleLHS=dotApps l, hintRuleRHS=dotApps r, hintRuleSide=hintRuleSide} ,m{hintRuleLHS=dotApps (l++[toNamed v1]), hintRuleRHS=dotApps (r++[toNamed v1]), hintRuleSide=hintRuleSide}] else if length l > 1 then [m{hintRuleLHS=dotApps l, hintRuleRHS=toNamed "id", hintRuleSide=hintRuleSide} ,m{hintRuleLHS=dotApps (l++[toNamed v1]), hintRuleRHS=toNamed v1, hintRuleSide=hintRuleSide}] else [] -- find a dot version of this rule, return the sequence of app prefixes, and the var dotVersion :: Exp_ -> [([Exp_], String)] dotVersion (view -> Var_ v) | isUnifyVar v = [([], v)] dotVersion (App l ls rs) = first (ls :) <$> dotVersion (fromParen rs) dotVersion (InfixApp l x op y) = (first (LeftSection l x op :) <$> dotVersion y) ++ (first (RightSection l op y:) <$> dotVersion x) dotVersion _ = [] --------------------------------------------------------------------- -- PERFORM THE MATCHING findIdeas :: [HintRule] -> Scope -> Module S -> Decl_ -> [Idea] findIdeas matches s _ decl = [ (idea (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes} | decl <- case decl of InstDecl{} -> children decl; _ -> [decl] , (parent,x) <- universeParentExp decl, not $ isParen x , m <- matches, Just (y,notes, subst) <- [matchIdea s decl m parent x] , let r = R.Replace R.Expr (toSS x) subst (prettyPrint $ hintRuleRHS m) ] matchIdea :: Scope -> Decl_ -> HintRule -> Maybe (Int, Exp_) -> Exp_ -> Maybe (Exp_, [Note], [(String, R.SrcSpan)]) matchIdea s decl HintRule{..} parent x = do let nm a b = scopeMatch (hintRuleScope,a) (s,b) u <- unifyExp nm True hintRuleLHS x u <- validSubst (=~=) u -- need to check free vars before unqualification, but after subst (with e) -- need to unqualify before substitution (with res) let e = substitute u hintRuleRHS res = addBracket parent $ performSpecial $ substitute u $ unqualify hintRuleScope s hintRuleRHS guard $ (freeVars e Set.\\ Set.filter (not . isUnifyVar) (freeVars hintRuleRHS)) `Set.isSubsetOf` freeVars x -- check no unexpected new free variables guard $ checkSide hintRuleSide $ ("original",x) : ("result",res) : fromSubst u guard $ checkDefine decl parent res return (res, hintRuleNotes, [(s, toSS pos) | (s, pos) <- fromSubst u, ann pos /= an]) --------------------------------------------------------------------- -- SIDE CONDITIONS checkSide :: Maybe Exp_ -> [(String, Exp_)] -> Bool checkSide x bind = maybe True f x where f (InfixApp _ x op y) | opExp op ~= "&&" = f x && f y | opExp op ~= "||" = f x || f y f (App _ x y) | x ~= "not" = not $ f y f (Paren _ x) = f x f (App _ cond (sub -> y)) | 'i':'s':typ <- fromNamed cond = isType typ y f (App _ (App _ cond (sub -> x)) (sub -> y)) | cond ~= "notIn" = and [x `notElem` universe y | x <- list x, y <- list y] | cond ~= "notEq" = x /=~= y f x | x ~= "noTypeCheck" = True f x | x ~= "noQuickCheck" = True f x = error $ "Hint.Match.checkSide, unknown side condition: " ++ prettyPrint x isType "Compare" x = True -- just a hint for proof stuff isType "Atom" x = isAtom x isType "WHNF" x = isWHNF x isType "Wildcard" x = any isFieldWildcard $ universeS x isType "Nat" (asInt -> Just x) | x >= 0 = True isType "Pos" (asInt -> Just x) | x > 0 = True isType "Neg" (asInt -> Just x) | x < 0 = True isType "NegZero" (asInt -> Just x) | x <= 0 = True isType ('L':'i':'t':typ@(_:_)) (Lit _ x) = head (words $ show x) == typ isType typ x = head (words $ show x) == typ asInt :: Exp_ -> Maybe Integer asInt (Paren _ x) = asInt x asInt (NegApp _ x) = negate <$> asInt x asInt (Lit _ (Int _ x _)) = Just x asInt _ = Nothing list :: Exp_ -> [Exp_] list (List _ xs) = xs list x = [x] sub :: Exp_ -> Exp_ sub = transform f where f (view -> Var_ x) | Just y <- lookup x bind = y f x = x -- does the result look very much like the declaration checkDefine :: Decl_ -> Maybe (Int, Exp_) -> Exp_ -> Bool checkDefine x Nothing y = fromNamed x /= fromNamed (transformBi unqual $ head $ fromApps y) checkDefine _ _ _ = True --------------------------------------------------------------------- -- TRANSFORMATION -- if it has _eval_ do evaluation on it performSpecial :: Exp_ -> Exp_ performSpecial = transform fNoParen . fEval where fEval (App _ e x) | e ~= "_eval_" = reduce x fEval x = x fNoParen (App _ e x) | e ~= "_noParen_" = fromParen x fNoParen x = x -- contract Data.List.foo ==> foo, if Data.List is loaded unqualify :: Scope -> Scope -> Exp_ -> Exp_ unqualify from to = transformBi f where f x@(UnQual _ (Ident _ s)) | isUnifyVar s = x f x = scopeMove (from,x) to addBracket :: Maybe (Int,Exp_) -> Exp_ -> Exp_ addBracket (Just (i,p)) c | needBracket i p c = Paren an c addBracket _ x = x hlint-2.0.11/src/Hint/ListRec.hs0000644000000000000000000001351613210071537014476 0ustar0000000000000000{-# LANGUAGE PatternGuards, ViewPatterns #-} {- map f [] = [] map f (x:xs) = f x : map f xs foldr f z [] = z foldr f z (x:xs) = f x (foldr f z xs) foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs -} {- f (x:xs) = negate x + f xs ; f [] = 0 -- f xs = foldr ((+) . negate) 0 xs f (x:xs) = x + 1 : f xs ; f [] = [] -- f xs = map (+ 1) xs f z (x:xs) = f (z*x) xs ; f z [] = z -- f z xs = foldl (*) z xs f a (x:xs) b = x + a + b : f a xs b ; f a [] b = [] -- f a xs b = map (\ x -> x + a + b) xs f [] a = return a ; f (x:xs) a = a + x >>= \fax -> f xs fax -- f xs a = foldM (+) a xs foos [] x = x; foos (y:ys) x = foo y $ foos ys x -- foos ys x = foldr foo x ys f [] y = y; f (x:xs) y = f xs $ g x y -- f xs y = foldl (flip g) y xs f [] y = y; f (x : xs) y = let z = g x y in f xs z -- f xs y = foldl (flip g) y xs f [] y = y; f (x:xs) y = f xs (f xs z) -} module Hint.ListRec(listRecHint) where import Hint.Type import Hint.Util import Data.List.Extra import Data.Maybe import Data.Ord import Data.Either.Extra import Control.Monad import Refact.Types hiding (RType(Match)) listRecHint :: DeclHint listRecHint _ _ = concatMap f . universe where f o = maybeToList $ do let x = o (x, addCase) <- findCase x (use,severity,x) <- matchListRec x let y = addCase x guard $ recursiveStr `notElem` varss y -- Maybe we can do better here maintaining source formatting? return $ idea severity ("Use " ++ use) o y [Replace Decl (toSS o) [] (prettyPrint y)] recursiveStr = "_recursive_" recursive = toNamed recursiveStr -- recursion parameters, nil-case, (x,xs,cons-case) -- for cons-case delete any recursive calls with xs from them -- any recursive calls are marked "_recursive_" data ListCase = ListCase [String] Exp_ (String,String,Exp_) deriving Show data BList = BNil | BCons String String deriving (Eq,Ord,Show) -- function name, parameters, list-position, list-type, body (unmodified) data Branch = Branch String [String] Int BList Exp_ deriving Show --------------------------------------------------------------------- -- MATCH THE RECURSION matchListRec :: ListCase -> Maybe (String,Severity,Exp_) matchListRec o@(ListCase vs nil (x,xs,cons)) | [] <- vs, nil ~= "[]", InfixApp _ lhs c rhs <- cons, opExp c ~= ":" , fromParen rhs =~= recursive, xs `notElem` vars lhs = Just $ (,,) "map" Warning $ appsBracket [toNamed "map", niceLambda [x] lhs, toNamed xs] | [] <- vs, App2 op lhs rhs <- view cons , vars op `disjoint` [x,xs] , fromParen rhs == recursive, xs `notElem` vars lhs = Just $ (,,) "foldr" Suggestion $ appsBracket [toNamed "foldr", niceLambda [x] $ appsBracket [op,lhs], nil, toNamed xs] | [v] <- vs, view nil == Var_ v, App _ r lhs <- cons, r =~= recursive , xs `notElem` vars lhs = Just $ (,,) "foldl" Suggestion $ appsBracket [toNamed "foldl", niceLambda [v,x] lhs, toNamed v, toNamed xs] | [v] <- vs, App _ ret res <- nil, ret ~= "return", res ~= "()" || view res == Var_ v , [Generator _ (view -> PVar_ b1) e, Qualifier _ (fromParen -> App _ r (view -> Var_ b2))] <- asDo cons , b1 == b2, r == recursive, xs `notElem` vars e , name <- "foldM" ++ ['_' | res ~= "()"] = Just $ (,,) name Suggestion $ appsBracket [toNamed name, niceLambda [v,x] e, toNamed v, toNamed xs] | otherwise = Nothing -- Very limited attempt to convert >>= to do, only useful for foldM/foldM_ asDo :: Exp_ -> [Stmt S] asDo (view -> App2 bind lhs (Lambda _ [v] rhs)) = [Generator an v lhs, Qualifier an rhs] asDo (Do _ x) = x asDo x = [Qualifier an x] --------------------------------------------------------------------- -- FIND THE CASE ANALYSIS findCase :: Decl_ -> Maybe (ListCase, Exp_ -> Decl_) findCase x = do FunBind _ [x1,x2] <- return x Branch name1 ps1 p1 c1 b1 <- findBranch x1 Branch name2 ps2 p2 c2 b2 <- findBranch x2 guard (name1 == name2 && ps1 == ps2 && p1 == p2) [(BNil, b1), (BCons x xs, b2)] <- return $ sortBy (comparing fst) [(c1,b1), (c2,b2)] b2 <- transformAppsM (delCons name1 p1 xs) b2 (ps,b2) <- return $ eliminateArgs ps1 b2 let ps12 = let (a,b) = splitAt p1 ps1 in map toNamed $ a ++ xs : b return (ListCase ps b1 (x,xs,b2) ,\e -> FunBind an [Match an (toNamed name1) ps12 (UnGuardedRhs an e) Nothing]) delCons :: String -> Int -> String -> Exp_ -> Maybe Exp_ delCons func pos var (fromApps -> (view -> Var_ x):xs) | func == x = do (pre, (view -> Var_ v):post) <- return $ splitAt pos xs guard $ v == var return $ apps $ recursive : pre ++ post delCons _ _ _ x = return x eliminateArgs :: [String] -> Exp_ -> ([String], Exp_) eliminateArgs ps cons = (remove ps, transform f cons) where args = [zs | z:zs <- map fromApps $ universeApps cons, z =~= recursive] elim = [all (\xs -> length xs > i && view (xs !! i) == Var_ p) args | (i,p) <- zip [0..] ps] ++ repeat False remove = concat . zipWith (\b x -> [x | not b]) elim f (fromApps -> x:xs) | x == recursive = apps $ x : remove xs f x = x --------------------------------------------------------------------- -- FIND A BRANCH findBranch :: Match S -> Maybe Branch findBranch x = do Match _ name ps (UnGuardedRhs _ bod) Nothing <- return x (a,b,c) <- findPat ps return $ Branch (fromNamed name) a b c $ simplifyExp bod findPat :: [Pat_] -> Maybe ([String], Int, BList) findPat ps = do ps <- mapM readPat ps [i] <- return $ findIndices isRight ps let (left,[right]) = partitionEithers ps return (left, i, right) readPat :: Pat_ -> Maybe (Either String BList) readPat (view -> PVar_ x) = Just $ Left x readPat (PParen _ (PInfixApp _ (view -> PVar_ x) (Special _ Cons{}) (view -> PVar_ xs))) = Just $ Right $ BCons x xs readPat (PList _ []) = Just $ Right BNil readPat _ = Nothing hlint-2.0.11/src/Hint/List.hs0000644000000000000000000001171613210071537014044 0ustar0000000000000000{-# LANGUAGE ViewPatterns, PatternGuards, FlexibleContexts #-} {- Find and match: yes = 1:2:[] -- [1,2] yes = ['h','e','l','l','o'] yes (1:2:[]) = 1 -- [1,2] yes ['h','e'] = 1 -- [a]++b -> a : b, but only if not in a chain of ++'s yes = [x] ++ xs -- x : xs no = "x" ++ xs no = [x] ++ xs ++ ys no = xs ++ [x] ++ ys yes = [if a then b else c] ++ xs -- (if a then b else c) : xs yes = [1] : [2] : [3] : [4] : [5] : [] -- [[1], [2], [3], [4], [5]] yes = if x == e then l2 ++ xs else [x] ++ check_elem xs -- x : check_elem xs data Yes = Yes (Maybe [Char]) -- Maybe String yes = y :: [Char] -> a -- String -> a instance C [Char] foo = [a b] ++ xs -- a b : xs foo = [myexpr | True, a] -- [myexpr | a] foo = [myexpr | False] -- [] -} module Hint.List(listHint) where import Control.Applicative import Hint.Type import Data.Maybe import Prelude import Refact.Types listHint :: DeclHint listHint _ _ = listDecl listDecl :: Decl_ -> [Idea] listDecl x = concatMap (listExp False) (childrenBi x) ++ stringType x ++ concatMap listPat (childrenBi x) ++ concatMap listComp (universeBi x) listComp :: Exp_ -> [Idea] listComp o@(ListComp a e xs) | "False" `elem` cons = [suggest "Short-circuited list comprehension" o (List an []) []] | "True" `elem` cons = [suggest "Redundant True guards" o o2 []] where o2 = ListComp a e $ filter ((/= Just "True") . qualCon) xs cons = mapMaybe qualCon xs qualCon (QualStmt _ (Qualifier _ (Con _ x))) = Just $ fromNamed x qualCon _ = Nothing listComp _ = [] -- boolean = are you in a ++ chain listExp :: Bool -> Exp_ -> [Idea] listExp b (fromParen -> x) = if null res then concatMap (listExp $ isAppend x) $ children x else [head res] where res = [suggest name x x2 [r] | (name,f) <- checks , Just (x2, subts, temp) <- [f b x] , let r = Replace Expr (toSS x) subts temp ] listPat :: Pat_ -> [Idea] listPat x = if null res then concatMap listPat $ children x else [head res] where res = [suggest name x x2 [r] | (name,f) <- pchecks , Just (x2, subts, temp) <- [f x] , let r = Replace Pattern (toSS x) subts temp ] isAppend (view -> App2 op _ _) = op ~= "++" isAppend _ = False checks = let (*) = (,) in drop 1 -- see #174 ["Use string literal" * useString ,"Use list literal" * useList ,"Use :" * useCons ] pchecks = let (*) = (,) in drop 1 -- see #174 ["Use string literal pattern" * usePString ,"Use list literal pattern" * usePList ] usePString (PList _ xs) | xs /= [], Just s <- mapM fromPChar xs = let literal = PLit an (Signless an) $ String an s (show s) in Just (literal, [], prettyPrint literal) usePString _ = Nothing usePList = fmap ( (\(e, s) -> (PList an e, map (fmap toSS) s, prettyPrint (PList an (map snd s)))) . unzip ) . f True ['a'..'z'] where f first _ x | x ~= "[]" = if first then Nothing else Just [] f first (ident: cs) (view -> PApp_ ":" [a,b]) = ((a, g ident a) :) <$> f False cs b f first _ _ = Nothing g :: Char -> Pat_ -> (String, Pat_) g c p = ([c], PVar (ann p) (toNamed [c])) useString b (List _ xs) | xs /= [], Just s <- mapM fromChar xs = let literal = Lit an $ String an s (show s) in Just (literal , [], prettyPrint literal) useString b _ = Nothing useList b = fmap ( (\(e, s) -> (List an e, map (fmap toSS) s, prettyPrint (List an (map snd s)))) . unzip ) . f True ['a'..'z'] where f first _ x | x ~= "[]" = if first then Nothing else Just [] f first (ident:cs) (view -> App2 c a b) | c ~= ":" = ((a, g ident a) :) <$> f False cs b f first _ _ = Nothing g :: Char -> Exp_ -> (String, Exp_) g c p = ([c], toNamed [c]) useCons False (view -> App2 op x y) | op ~= "++" , Just (x2, build) <- f x , not $ isAppend y = Just (gen (build x2) y , [("x", toSS x2), ("xs", toSS y)] , prettyPrint $ gen (build $ toNamed "x") (toNamed "xs")) where f (List _ [x]) = Just (x, \v -> if isApp x then v else paren v) f _ = Nothing gen x = InfixApp an x (QConOp an $ list_cons_name an) useCons _ _ = Nothing typeListChar = TyList an (TyCon an (toNamed "Char")) typeString = TyCon an (toNamed "String") stringType :: Decl_ -> [Idea] stringType x = case x of InstDecl _ _ _ x -> f x _ -> f x where f x = concatMap g $ childrenBi x g :: Type_ -> [Idea] g e@(fromTyParen -> x) = [suggest "Use String" x (transform f x) rs | not . null $ rs] where f x = if x =~= typeListChar then typeString else x rs = [Replace Type (toSS t) [] (prettyPrint typeString) | t <- universe x, t =~= typeListChar] hlint-2.0.11/src/Hint/Lambda.hs0000644000000000000000000001512113210071537014303 0ustar0000000000000000{-# LANGUAGE ViewPatterns, PatternGuards #-} {- Concept: Remove all the lambdas you can be inserting only sections Never create a right section with +-# as the operator (they are misparsed) Rules: fun a = \x -> y -- promote lambdas, provided no where's outside the lambda fun x = y x -- eta reduce, x /= mr and foo /= symbol \x -> y x -- eta reduce ((#) x) ==> (x #) -- rotate operators (flip op x) ==> (`op` x) -- rotate operators \x y -> x + y ==> (+) -- insert operator \x y -> op y x ==> flip op \x -> x + y ==> (+ y) -- insert section, \x -> op x y ==> (`op` y) -- insert section \x -> y + x ==> (y +) -- insert section \x -> \y -> ... ==> \x y -- lambda compression \x -> (x +) ==> (+) -- operator reduction f a = \x -> x + x -- f a x = x + x f a = \a -> a + a -- f _ a = a + a f a = \x -> x + x where _ = test f (test -> a) = \x -> x + x f = \x -> x + x -- f x = x + x fun x y z = f x y z -- fun = f fun x y z = f x x y z -- fun x = f x x fun x y z = f g z -- fun x y = f g fun mr = y mr fun x = f . g $ x -- fun = f . g f = foo (\y -> g x . h $ y) -- g x . h f = foo ((*) x) -- (x *) f = (*) x f = foo (flip op x) -- (`op` x) f = flip op x f = foo (flip (*) x) -- (* x) f = foo (flip (-) x) f = foo (\x y -> fun x y) -- @Warning fun f = foo (\x y -> x + y) -- (+) f = foo (\x -> x * y) -- @Suggestion (* y) f = foo (\x -> x # y) f = foo (\x -> \y -> x x y y) -- \x y -> x x y y f = foo (\x -> \x -> foo x x) -- \_ x -> foo x x f = foo (\(foo -> x) -> \y -> x x y y) f = foo (\(x:xs) -> \x -> foo x x) -- \(_:xs) x -> foo x x f = foo (\x -> \y -> \z -> x x y y z z) -- \x y z -> x x y y z z x ! y = fromJust $ lookup x y f = foo (\i -> writeIdea (getClass i) i) f = bar (flip Foo.bar x) -- (`Foo.bar` x) f = a b (\x -> c x d) -- (`c` d) yes = \x -> a x where -- a yes = \x y -> op y x where -- flip op f = \y -> nub $ reverse y where -- nub . reverse f = \z -> foo $ bar $ baz z where -- foo . bar . baz f = \z -> foo $ bar x $ baz z where -- foo . bar x . baz f = \z -> foo $ z $ baz z where f = \x -> bar map (filter x) where -- bar map . filter f = bar &+& \x -> f (g x) foo = [\column -> set column [treeViewColumnTitle := printf "%s (match %d)" name (length candidnates)]] foo = [\x -> x] foo = [\m x -> insert x x m] foo a b c = bar (flux ++ quux) c where flux = a -- foo a b = bar (flux ++ quux) foo a b c = bar (flux ++ quux) c where flux = c yes = foo (\x -> Just x) -- @Warning Just foo = bar (\x -> (x `f`)) -- f baz = bar (\x -> (x +)) -- (+) yes = blah (\ x -> case x of A -> a; B -> b) -- \ case A -> a; B -> b no = blah (\ x -> case x of A -> a x; B -> b x) -} module Hint.Lambda(lambdaHint) where import Hint.Util import Hint.Type import Util import Data.List.Extra import Data.Maybe import Refact.Types hiding (RType(Match)) lambdaHint :: DeclHint lambdaHint _ _ x = concatMap (uncurry lambdaExp) (universeParentBi x) ++ concatMap lambdaDecl (universe x) lambdaDecl :: Decl_ -> [Idea] lambdaDecl (toFunBind -> o@(FunBind loc1 [Match _ name pats (UnGuardedRhs loc2 bod) bind])) | isNothing bind, isLambda $ fromParen bod, null (universeBi pats :: [Exp_]) = [warn "Redundant lambda" o (gen pats bod) [Replace Decl (toSS o) s1 t1]] | length pats2 < length pats, pvars (drop (length pats2) pats) `disjoint` varss bind = [warn "Eta reduce" (reform pats bod) (reform pats2 bod2) [ -- Disabled, see apply-refact #3 -- Replace Decl (toSS $ reform pats bod) s2 t2]] ]] where reform p b = FunBind loc [Match an name p (UnGuardedRhs an b) Nothing] loc = setSpanInfoEnd loc1 $ srcSpanEnd $ srcInfoSpan loc2 gen ps = uncurry reform . fromLambda . Lambda an ps (finalpats, body) = fromLambda . Lambda an pats $ bod (pats2, bod2) = etaReduce pats bod template fps b = prettyPrint $ reform (zipWith munge ['a'..'z'] fps) (toNamed "body") munge :: Char -> Pat_ -> Pat_ munge ident p@(PWildCard _) = p munge ident p = PVar (ann p) (Ident (ann p) [ident]) subts fps b = ("body", toSS b) : zipWith (\x y -> ([x],y)) ['a'..'z'] (map toSS fps) s1 = subts finalpats body --s2 = subts pats2 bod2 t1 = template finalpats body --t2 = template pats2 bod2 lambdaDecl _ = [] setSpanInfoEnd ssi (line, col) = ssi{srcInfoSpan = (srcInfoSpan ssi){srcSpanEndLine=line, srcSpanEndColumn=col}} etaReduce :: [Pat_] -> Exp_ -> ([Pat_], Exp_) etaReduce ps (App _ x (Var _ (UnQual _ (Ident _ y)))) | ps /= [], PVar _ (Ident _ p) <- last ps, p == y, p /= "mr", y `notElem` vars x = etaReduce (init ps) x etaReduce ps (InfixApp a x (isDol -> True) y) = etaReduce ps (App a x y) etaReduce ps x = (ps,x) --Section refactoring is not currently implemented. lambdaExp :: Maybe Exp_ -> Exp_ -> [Idea] lambdaExp p o@(Paren _ (App _ v@(Var l (UnQual _ (Symbol _ x))) y)) | isAtom y, allowLeftSection x = [suggestN "Use section" o (exp y x)] -- [Replace Expr (toSS o) subts template]] where exp op rhs = LeftSection an op (toNamed rhs) -- template = prettyPrint (exp (toNamed "a") "*") -- subts = [("a", toSS y), ("*", toSS v)] lambdaExp p o@(Paren _ (App _ (App _ (view -> Var_ "flip") (Var _ x)) y)) | allowRightSection $ fromNamed x = [suggestN "Use section" o $ RightSection an (QVarOp an x) y] lambdaExp p o@Lambda{} | maybe True (not . isInfixApp) p, (res, refact) <- niceLambdaR [] o, not $ isLambda res = [(if isVar res || isCon res then warn else suggest) "Avoid lambda" o res (refact $ toSS o)] lambdaExp p o@(Lambda _ pats x) | isLambda (fromParen x), null (universeBi pats :: [Exp_]), maybe True (not . isLambda) p = [suggest "Collapse lambdas" o (Lambda an pats body) [Replace Expr (toSS o) subts template]] where (pats, body) = fromLambda o template = prettyPrint $ Lambda an (zipWith munge ['a'..'z'] pats) (toNamed "body") munge :: Char -> Pat_ -> Pat_ munge ident p@(PWildCard _) = p munge ident p = PVar (ann p) (Ident (ann p) [ident]) subts = ("body", toSS body) : zipWith (\x y -> ([x],y)) ['a'..'z'] (map toSS pats) lambdaExp p o@(Lambda _ [view -> PVar_ u] (Case _ (view -> Var_ v) alts)) | u == v, u `notElem` vars alts = [suggestN "Use lambda-case" o $ LCase an alts] lambdaExp _ _ = [] -- replace any repeated pattern variable with _ fromLambda :: Exp_ -> ([Pat_], Exp_) fromLambda (Lambda _ ps1 (fromLambda . fromParen -> (ps2,x))) = (transformBi (f $ pvars ps2) ps1 ++ ps2, x) where f bad x@PVar{} | prettyPrint x `elem` bad = PWildCard an f bad x = x fromLambda x = ([], x) hlint-2.0.11/src/Hint/Import.hs0000644000000000000000000003217013210071537014400 0ustar0000000000000000{-# LANGUAGE PatternGuards, ScopedTypeVariables, RecordWildCards #-} {- Reduce the number of import declarations. Two import declarations can be combined if: (note, A[] is A with whatever import list, or none) import A[]; import A[] = import A[] import A(B); import A(C) = import A(B,C) import A; import A(C) = import A import A; import A hiding (C) = import A import A[]; import A[] as Y = import A[] as Y import A; import A -- import A import A; import A; import A -- import A import A(Foo) ; import A -- import A import A ;import A(Foo) -- import A import A(Bar(..)); import {-# SOURCE #-} A import A; import B import A(B) ; import A(C) -- import A(B,C) import A; import A hiding (C) -- import A import A; import A as Y -- import A as Y import A; import qualified A as Y import A as B; import A as C import A as A -- import A import qualified A as A -- import qualified A import A; import B; import A -- import A import qualified A; import A import B; import A; import A -- import A import A hiding(Foo); import A hiding(Bar) import List -- import Data.List import qualified List -- import qualified Data.List as List import Char(foo) -- import Data.Char(foo) import IO(foo) import IO as X -- import System.IO as X; import System.IO.Error as X; import Control.Exception as X (bracket,bracket_) import A hiding (a) -- import A import A hiding (a, b); foo = a -- import A hiding (a) import A hiding (a, b); foo = A.a -- import A hiding (a) import A as B hiding (a) -- import A as B import A as B hiding (a, b); foo = a -- import A as B hiding (a) import A as B hiding (a, b); foo = B.a -- import A as B hiding (a) import qualified A hiding (a) -- import qualified A import qualified A hiding (a, b); foo = A.a -- import qualified A hiding (a) import qualified A as B hiding (a, b); foo = B.a -- import qualified A as B hiding (a) import A hiding ((+)) -- import A import A hiding ((+), (*)); foo = (+) -- import A hiding ((+)) import A hiding ((+), (*)); foo = (+x) -- import A hiding ((+)) import A hiding ((+), (*)); foo = (x+) -- import A hiding ((+)) import A hiding ((+), (*)); foo = x+y -- import A hiding ((+)) import A hiding ((+), (*)); foo = (A.+) -- import A hiding ((+)) import A hiding ((+), (*)); foo = (A.+ x) -- import A hiding ((+)) import A hiding ((+), (*)); foo = (x A.+) -- import A hiding ((+)) import A hiding ((+), (*)); foo = x A.+ y -- import A hiding ((+)) import A as B hiding ((+)) -- import A as B import A as B hiding ((+), (*)); foo = (+) -- import A as B hiding ((+)) import A as B hiding ((+), (*)); foo = (x+) -- import A as B hiding ((+)) import A as B hiding ((+), (*)); foo = (+x) -- import A as B hiding ((+)) import A as B hiding ((+), (*)); foo = x+y -- import A as B hiding ((+)) import A as B hiding ((+), (*)); foo = (B.+) -- import A as B hiding ((+)) import A as B hiding ((+), (*)); foo = (x B.+) -- import A as B hiding ((+)) import A as B hiding ((+), (*)); foo = (B.+ x) -- import A as B hiding ((+)) import A as B hiding ((+), (*)); foo = x B.+ y -- import A as B hiding ((+)) import qualified A hiding ((+)) -- import qualified A import qualified A hiding ((+), (*)); foo = (A.+) -- import qualified A hiding ((+)) import qualified A hiding ((+), (*)); foo = (x A.+) -- import qualified A hiding ((+)) import qualified A hiding ((+), (*)); foo = (A.+ x) -- import qualified A hiding ((+)) import qualified A hiding ((+), (*)); foo = x A.+ y -- import qualified A hiding ((+)) import qualified A as B hiding ((+), (*)); foo = (B.+) -- import qualified A as B hiding ((+)) import qualified A as B hiding ((+), (*)); foo = (x B.+) -- import qualified A as B hiding ((+)) import qualified A as B hiding ((+), (*)); foo = (B.+ x) -- import qualified A as B hiding ((+)) import qualified A as B hiding ((+), (*)); foo = x B.+ y -- import qualified A as B hiding ((+)) module Foo (a) where; import A hiding (a) module Foo (a) where; import A hiding (a, b) -- import A hiding (a) module Foo (A.a) where; import A hiding (a, b) -- import A hiding (a) module Foo (a) where; import A as B hiding (a) module Foo (a) where; import A as B hiding (a, b) -- import A as B hiding (a) module Foo (B.a) where; import A as B hiding (a, b) -- import A as B hiding (a) module Foo (a) where; import qualified A hiding (a) -- import qualified A module Foo (A.a) where; import qualified A hiding (a, b) -- import qualified A hiding (a) module Foo (B.a) where; import qualified A as B hiding (a, b) -- import qualified A as B hiding (a) module Foo (module A) where; import A hiding (a, b, c) module Foo (module B) where; import A as B hiding (a, b, c) module Foo (module A) where; import qualified A hiding (a, b, c) -- import qualified A module Foo (module B) where; import qualified A as B hiding (a, b, c) -- import qualified A as B module Foo ((+)) where; import A hiding ((+)) module Foo ((+)) where; import A hiding ((+), (*)) -- import A hiding ((+)) module Foo ((A.+)) where; import A hiding ((+), (*)) -- import A hiding ((+)) module Foo ((+)) where; import A as B hiding ((+)) module Foo ((+)) where; import A as B hiding ((+), (*)) -- import A as B hiding ((+)) module Foo ((B.+)) where; import A as B hiding ((+), (*)) -- import A as B hiding ((+)) module Foo ((+)) where; import qualified A hiding ((+)) -- import qualified A module Foo ((A.+)) where; import qualified A hiding ((+), (*)) -- import qualified A hiding ((+)) module Foo ((B.+)) where; import qualified A as B hiding ((+), (*)) -- import qualified A as B hiding ((+)) module Foo (module A) where; import A hiding ((+), (*), (/)) module Foo (module B) where; import A as B hiding ((+), (*), (/)) module Foo (module A) where; import qualified A hiding ((+), (*), (/)) -- import qualified A module Foo (module B) where; import qualified A as B hiding ((+), (*), (/)) -- import qualified A as B {-# LANGUAGE QuasiQuotes #-}; import A hiding (a); [a||] {-# LANGUAGE QuasiQuotes #-}; import A hiding (a); [A.a||] {-# LANGUAGE QuasiQuotes #-}; import A as B hiding (a); [B.a||] {-# LANGUAGE QuasiQuotes #-}; import qualified A hiding (a); [A.a||] {-# LANGUAGE QuasiQuotes #-}; import qualified A as B hiding (a); [B.a||] -} module Hint.Import(importHint) where import Control.Applicative import Data.Tuple.Extra import Hint.Type import Refact.Types hiding (ModuleName) import qualified Refact.Types as R import Data.List.Extra import Data.Either (partitionEithers) import Data.Maybe import Prelude import Data.Map (Map) import qualified Data.Map as Map import Data.Set (Set) import qualified Data.Set as Set importHint :: ModuHint importHint _ x = concatMap (wrap . snd) (groupSort [((fromNamed $ importModule i,importPkg i),i) | i <- universeBi x, not $ importSrc i]) ++ concatMap (\x -> hierarchy x ++ combine1 x ++ hidden reexported unqual quals x) (universeBi x) where -- Names of all re-exported modules reexported :: Set String reexported = Set.fromList [ fromModuleName n | EModuleContents _ n <- universeBi x ] -- Unqualified expressions and exported expressions unqual :: Set String unqual = Set.fromList (mapMaybe f qnames) `Set.union` Set.fromList qqUnqual where f :: QName S -> Maybe String f (UnQual _ n) = Just (fromNamed n) f _ = Nothing -- Qualified expressions and exported expressions quals :: Map String (Set String) quals = Map.fromListWith Set.union (map (second Set.singleton) qqQuals ++ mapMaybe f qnames) where f (Qual _ m n) = Just (fromModuleName m, Set.singleton (fromNamed n)) f _ = Nothing -- Unqualified quasi-quoters like [foo|...|] qqUnqual :: [String] -- Qualified quasi-quoters like [Foo.bar|...|] qqQuals :: [(String, String)] (qqUnqual, qqQuals) = partitionEithers [ f n | QuasiQuote (_ :: S) n _ <- universeBi x ] where f :: String -> Either String (String, String) f n = maybe (Left n) Right (stripInfixEnd "." n) qnames :: [QName S] qnames = concat [ [ n | Var (_ :: S) n <- universeBi x ] , [ n | VarQuote (_ :: S) n <- universeBi x ] , [ n | QVarOp (_ :: S) n <- universeBi x ] , [ n | EVar (_ :: S) n <- universeBi x ] ] wrap :: [ImportDecl S] -> [Idea] wrap o = [ rawIdea Warning "Use fewer imports" (srcInfoSpan $ ann $ head o) (f o) (Just $ f x) [] rs | Just (x, rs) <- [simplify o]] where f = unlines . map prettyPrint simplify :: [ImportDecl S] -> Maybe ([ImportDecl S], [Refactoring R.SrcSpan]) simplify [] = Nothing simplify (x:xs) = case simplifyHead x xs of Nothing -> first (x:) <$> simplify xs Just (xs, rs) -> Just $ maybe (xs, rs) (second (++ rs)) $ simplify xs simplifyHead :: ImportDecl S -> [ImportDecl S] -> Maybe ([ImportDecl S], [Refactoring R.SrcSpan]) simplifyHead x [] = Nothing simplifyHead x (y:ys) = case combine x y of Nothing -> first (y:) <$> simplifyHead x ys Just (xy, rs) -> Just (xy : ys, rs) combine :: ImportDecl S -> ImportDecl S -> Maybe (ImportDecl S, [Refactoring R.SrcSpan]) combine x y | qual, as, specs = Just (x, [Delete Import (toSS y)]) | qual, as, Just (ImportSpecList _ False xs) <- importSpecs x, Just (ImportSpecList _ False ys) <- importSpecs y = let newImp = x{importSpecs = Just $ ImportSpecList an False $ nub_ $ xs ++ ys} in Just (newImp, [ Replace Import (toSS x) [] (prettyPrint newImp) , Delete Import (toSS y) ] ) | qual, as, isNothing (importSpecs x) || isNothing (importSpecs y) = let (newImp, toDelete) = if isNothing (importSpecs x) then (x, y) else (y, x) in Just (newImp, [Delete Import (toSS toDelete)]) | not (importQualified x), qual, specs, length ass == 1 = let (newImp, toDelete) = if isJust (importAs x) then (x, y) else (y, x) in Just (newImp, [Delete Import (toSS toDelete)]) where qual = importQualified x == importQualified y as = importAs x `eqMaybe` importAs y ass = mapMaybe importAs [x,y] specs = importSpecs x `eqMaybe` importSpecs y combine _ _ = Nothing combine1 :: ImportDecl S -> [Idea] combine1 i@ImportDecl{..} | Just (dropAnn importModule) == fmap dropAnn importAs = [suggest "Redundant as" i i{importAs=Nothing} [RemoveAsKeyword (toSS i)]] combine1 _ = [] newNames = let (*) = flip (,) in ["Control" * "Monad" ,"Data" * "Char" ,"Data" * "List" ,"Data" * "Maybe" ,"Data" * "Ratio" ,"System" * "Directory" -- Special, see bug #393 -- ,"System" * "IO" -- Do not encourage use of old-locale/old-time over haskell98 -- ,"System" * "Locale" -- ,"System" * "Time" ] hierarchy :: ImportDecl S -> [Idea] hierarchy i@ImportDecl{importModule=m@(ModuleName _ x),importPkg=Nothing} | Just y <- lookup x newNames = let newModuleName = y ++ "." ++ x r = [Replace R.ModuleName (toSS m) [] newModuleName] in [suggest "Use hierarchical imports" i (desugarQual i){importModule=ModuleName an newModuleName} r] -- import IO is equivalent to -- import System.IO, import System.IO.Error, import Control.Exception(bracket, bracket_) hierarchy i@ImportDecl{importModule=ModuleName _ "IO", importSpecs=Nothing,importPkg=Nothing} = [rawIdeaN Suggestion "Use hierarchical imports" (srcInfoSpan $ ann i) (trimStart $ prettyPrint i) ( Just $ unlines $ map (trimStart . prettyPrint) [f "System.IO" Nothing, f "System.IO.Error" Nothing ,f "Control.Exception" $ Just $ ImportSpecList an False [IVar an $ toNamed x | x <- ["bracket","bracket_"]]]) []] where f a b = (desugarQual i){importModule=ModuleName an a, importSpecs=b} hierarchy _ = [] -- import qualified X ==> import qualified X as X desugarQual :: ImportDecl S -> ImportDecl S desugarQual x | importQualified x && isNothing (importAs x) = x{importAs=Just (importModule x)} | otherwise = x -- Suggest removing unnecessary "hiding" clauses in imports. Currently this only -- works for expressions. hidden :: Set String -> Set String -> Map String (Set String) -> ImportDecl S -> [Idea] hidden reexported unqual quals i@ImportDecl{importSpecs = Just (ImportSpecList loc True xs)} -- If the module is re-exported and not imported qualified, we can't prune -- any identifiers from the hiding clause | not (importQualified i) && as `Set.member` reexported = [] | otherwise = case partition isUsed xs of (_, []) -> [] ([], _) -> [suggest "Unnecessary hiding" i i{importSpecs = Nothing} [Delete Import (toSS i)]] (xs, _) -> let newImp = i{importSpecs = Just (ImportSpecList loc True xs)} in [suggest "Unnecessary hiding" i newImp [Replace Import (toSS i) [] (prettyPrint newImp)]] where isUsed :: ImportSpec S -> Bool isUsed (IVar _ n) = Set.member (fromNamed n) vars isUsed _ = True vars :: Set String vars = if importQualified i then qual else qual `Set.union` unqual qual :: Set String qual = fromMaybe Set.empty (Map.lookup as quals) as :: String as = fromModuleName (fromMaybe (importModule i) (importAs i)) hidden _ _ _ _ = [] hlint-2.0.11/src/Hint/Extensions.hs0000644000000000000000000002434113210071537015266 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} {- Suggest removal of unnecessary extensions i.e. They have {-# LANGUAGE RecursiveDo #-} but no mdo keywords {-# LANGUAGE Arrows #-} \ f = id -- {-# LANGUAGE TotallyUnknown #-} \ f = id {-# LANGUAGE Foo, ParallelListComp, ImplicitParams #-} \ f = [(a,c) | a <- b | c <- d] -- {-# LANGUAGE Foo, ParallelListComp #-} {-# LANGUAGE EmptyDataDecls #-} \ data Foo {-# LANGUAGE TemplateHaskell #-} \ $(deriveNewtypes typeInfo) {-# LANGUAGE TemplateHaskell #-} \ main = foo ''Bar {-# LANGUAGE PatternGuards #-} \ test = case x of _ | y <- z -> w {-# LANGUAGE TemplateHaskell,EmptyDataDecls #-} \ $(fmap return $ dataD (return []) (mkName "Void") [] [] []) {-# LANGUAGE RecursiveDo #-} \ main = mdo x <- y; return y {-# LANGUAGE RecursiveDo #-} \ main = do {rec {x <- return 1}; print x} {-# LANGUAGE ImplicitParams, BangPatterns #-} \ sort :: (?cmp :: a -> a -> Bool) => [a] -> [a] \ sort !f = undefined {-# LANGUAGE KindSignatures #-} \ data Set (cxt :: * -> *) a = Set [a] {-# LANGUAGE RecordWildCards #-} \ record field = Record{..} {-# LANGUAGE RecordWildCards #-} \ record = 1 -- {-# LANGUAGE UnboxedTuples #-} \ record = 1 -- {-# LANGUAGE TemplateHaskell #-} \ foo {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ record = 1 -- {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ newtype Foo = Foo Int deriving Data -- {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ data Foo = Foo Int deriving Data -- {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ newtype Foo = Foo Int deriving Class -- {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ data Foo = Foo Int deriving Class -- {-# LANGUAGE DeriveFunctor #-} \ data Foo = Foo Int deriving Functor {-# LANGUAGE DeriveFunctor #-} \ newtype Foo = Foo Int deriving Functor {-# LANGUAGE GeneralizedNewtypeDeriving #-} \ newtype Foo = Foo Int deriving Functor {-# LANGUAGE GeneralizedNewtypeDeriving #-} \ newtype Foo = Foo Int deriving Data -- {-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving, StandaloneDeriving #-} \ deriving instance Functor Bar {-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving, StandaloneDeriving #-} \ deriving instance Show Bar -- {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-} \ newtype Micro = Micro Int deriving Generic -- {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} \ instance Class Int where {newtype MyIO a = MyIO a deriving NewClass} {-# LANGUAGE UnboxedTuples #-} \ f :: Int -> (# Int, Int #) {-# LANGUAGE UnboxedTuples #-} \ f :: x -> (x, x); f x = (x, x) -- {-# LANGUAGE DefaultSignatures #-} \ class Val a where; val :: a -- {-# LANGUAGE DefaultSignatures #-} \ class Val a where; val :: a; default val :: Int {-# LANGUAGE TypeApplications #-} \ foo = id -- {-# LANGUAGE TypeApplications #-} \ foo = id @Int {-# LANGUAGE LambdaCase #-} \ foo = id -- {-# LANGUAGE LambdaCase #-} \ foo = \case () -> () {-# LANGUAGE NumDecimals #-} \ foo = 12.3e2 {-# LANGUAGE NumDecimals #-} \ foo = id -- {-# LANGUAGE NumDecimals #-} \ foo = 12.345e2 -- {-# LANGUAGE TupleSections #-} \ main = map (,1,2) xs {-# LANGUAGE TupleSections #-} \ main = id -- {-# LANGUAGE OverloadedStrings #-} \ main = "test" {-# LANGUAGE OverloadedStrings #-} \ main = id -- {-# LANGUAGE DeriveAnyClass #-} \ main = id -- {-# LANGUAGE DeriveAnyClass #-} \ data Foo = Foo deriving Bob {-# LANGUAGE DeriveAnyClass #-} \ data Foo a = Foo a deriving (Eq,Data,Functor) -- {-# LANGUAGE MagicHash #-} \ foo# = id {-# LANGUAGE MagicHash #-} \ main = "foo"# {-# LANGUAGE MagicHash #-} \ main = 5# {-# LANGUAGE MagicHash #-} \ main = 'a'# {-# LANGUAGE MagicHash #-} \ main = 5.6# {-# LANGUAGE MagicHash #-} \ foo = id -- -} module Hint.Extensions(extensionsHint) where import Hint.Type import Control.Monad.Extra import Data.Maybe import Data.List.Extra import Data.Ratio import Data.Data import Refact.Types import Data.Monoid import Prelude extensionsHint :: ModuHint extensionsHint _ x = [rawIdea Warning "Unused LANGUAGE pragma" (srcInfoSpan sl) (prettyPrint o) (Just newPragma) (warnings old new) [refact] | not $ used TemplateHaskell x -- if TH is on, can use all other extensions programmatically , o@(LanguagePragma sl exts) <- modulePragmas x , let old = map (parseExtension . prettyPrint) exts , let new = minimalExtensions x old , let newPragma = if null new then "" else prettyPrint $ LanguagePragma sl $ map (toNamed . prettyExtension) new , let refact = ModifyComment (toSS o) newPragma , sort new /= sort old] minimalExtensions :: Module_ -> [Extension] -> [Extension] minimalExtensions x es = nub $ concatMap f es where f e = [e | usedExt e x] -- RecordWildCards implies DisambiguateRecordFields, but most people probably don't want it warnings old new | wildcards `elem` old && wildcards `notElem` new = [Note "you may need to add DisambiguateRecordFields"] where wildcards = EnableExtension RecordWildCards warnings _ _ = [] deriveHaskell = ["Eq","Ord","Enum","Ix","Bounded","Read","Show"] deriveGenerics = ["Data","Typeable","Generic","Generic1","Lift"] deriveCategory = ["Functor","Foldable","Traversable"] -- | Classes that can't require newtype deriving noGeneralizedNewtypeDeriving = delete "Enum" deriveHaskell ++ -- Enum can't always be derived on a newtype deriveGenerics -- Generics stuff can't newtype derive since it has the ctor in it -- | Classes that can't require DeriveAnyClass noDeriveAnyClass = deriveHaskell ++ deriveGenerics ++ deriveCategory usedExt :: Extension -> Module_ -> Bool usedExt (EnableExtension x) = used x usedExt (UnknownExtension "NumDecimals") = hasS isWholeFrac usedExt (UnknownExtension "DeriveLift") = hasDerive ["Lift"] usedExt (UnknownExtension "DeriveAnyClass") = any (`notElem` noDeriveAnyClass) . (\Derives{..} -> derivesNewType ++ derivesData) . derives usedExt _ = const True used :: KnownExtension -> Module_ -> Bool used RecursiveDo = hasS isMDo ||^ hasS isRecStmt used ParallelListComp = hasS isParComp used FunctionalDependencies = hasT (un :: FunDep S) used ImplicitParams = hasT (un :: IPName S) used TypeApplications = hasS isTypeApp used EmptyDataDecls = hasS f where f (DataDecl _ _ _ _ [] _) = True f (GDataDecl _ _ _ _ _ [] _) = True f _ = False used KindSignatures = hasT (un :: Kind S) used BangPatterns = hasS isPBangPat used TemplateHaskell = hasT2 (un :: (Bracket S, Splice S)) ||^ hasS f ||^ hasS isSpliceDecl where f VarQuote{} = True f TypQuote{} = True f _ = False used ForeignFunctionInterface = hasT (un :: CallConv S) used PatternGuards = hasS f where f (GuardedRhs _ xs _) = g xs g [] = False g [Qualifier{}] = False g _ = True used StandaloneDeriving = hasS isDerivDecl used PatternSignatures = hasS isPatTypeSig used RecordWildCards = hasS isPFieldWildcard ||^ hasS isFieldWildcard used RecordPuns = hasS isPFieldPun ||^ hasS isFieldPun used UnboxedTuples = has (not . isBoxed) used PackageImports = hasS (isJust . importPkg) used QuasiQuotes = hasS isQuasiQuote ||^ hasS isTyQuasiQuote used ViewPatterns = hasS isPViewPat used DefaultSignatures = hasS isClsDefSig used DeriveDataTypeable = hasDerive ["Data","Typeable"] used DeriveFunctor = hasDerive ["Functor"] used DeriveFoldable = hasDerive ["Foldable"] used DeriveTraversable = hasDerive ["Traversable"] used DeriveGeneric = hasDerive ["Generic","Generic1"] used GeneralizedNewtypeDeriving = any (`notElem` noGeneralizedNewtypeDeriving) . (\Derives{..} -> derivesNewType ++ derivesStandalone) . derives used LambdaCase = hasS isLCase used TupleSections = hasS isTupleSection used OverloadedStrings = hasS isString used Arrows = hasS f where f Proc{} = True f LeftArrApp{} = True f RightArrApp{} = True f LeftArrHighApp{} = True f RightArrHighApp{} = True f _ = False used TransformListComp = hasS f where f QualStmt{} = False f _ = True used MagicHash = hasS f ||^ hasS isPrimLiteral where f (Ident _ s) = "#" `isSuffixOf` s f _ = False -- for forwards compatibility, if things ever get added to the extension enumeration used x = usedExt $ UnknownExtension $ show x hasDerive :: [String] -> Module_ -> Bool hasDerive want m = any (`elem` want) $ derivesNewType ++ derivesData ++ derivesStandalone where Derives{..} = derives m data Derives = Derives {derivesNewType :: [String] ,derivesData :: [String] ,derivesStandalone :: [String] } instance Monoid Derives where mempty = Derives [] [] [] mappend (Derives x1 x2 x3) (Derives y1 y2 y3) = Derives (x1++y1) (x2++y2) (x3++y3) -- | What is derived on newtype, and on data type -- 'deriving' declarations may be on either, so we approximate as both newtype and data derives :: Module_ -> Derives derives m = mconcat $ map decl (childrenBi m) ++ map idecl (childrenBi m) where idecl :: InstDecl S -> Derives idecl (InsData _ dn _ _ ds) = g dn ds idecl (InsGData _ dn _ _ _ ds) = g dn ds idecl _ = mempty decl :: Decl_ -> Derives decl (DataDecl _ dn _ _ _ ds) = g dn ds decl (GDataDecl _ dn _ _ _ _ ds) = g dn ds decl (DataInsDecl _ dn _ _ ds) = g dn ds decl (GDataInsDecl _ dn _ _ _ ds) = g dn ds decl (DerivDecl _ _ hd) = mempty{derivesStandalone=[ir hd]} decl _ = mempty g dn ds = if isNewType dn then mempty{derivesNewType=xs} else mempty{derivesData=xs} where xs = maybe [] (map ir . fromDeriving) ds ir (IRule _ _ _ x) = ih x ir (IParen _ x) = ir x ih (IHCon _ a) = prettyPrint $ unqual a ih (IHInfix _ _ a) = prettyPrint $ unqual a ih (IHParen _ a) = ih a ih (IHApp _ a _) = ih a un = undefined hasT t x = not $ null (universeBi x `asTypeOf` [t]) hasT2 ~(t1,t2) = hasT t1 ||^ hasT t2 hasS :: (Data x, Data (f S)) => (f S -> Bool) -> x -> Bool hasS test = any test . universeBi has f = any f . universeBi -- Only whole number fractions are permitted by NumDecimals extension. -- Anything not-whole raises an error. isWholeFrac :: Literal S -> Bool isWholeFrac (Frac _ v _) = denominator v == 1 isWholeFrac _ = False hlint-2.0.11/src/Hint/Export.hs0000644000000000000000000000172513210071537014411 0ustar0000000000000000{- Suggest using better export declarations main = 1 module Foo where foo = 1 -- module Foo(module Foo) where module Foo(foo) where foo = 1 module Foo(module Foo) where foo = 1 -- @Ignore module Foo(...) where module Foo(module Foo, foo) where foo = 1 -- module Foo(..., foo) where -} module Hint.Export(exportHint) where import Hint.Type exportHint :: ModuHint exportHint _ (Module _ (Just o@(ModuleHead a name warning exports)) _ _ _) | Nothing <- exports = let o2 = ModuleHead a name warning $ Just $ ExportSpecList a [EModuleContents a name] in [(ignore "Use module export list" o o2 []){ideaNote = [Note "An explicit list is usually better"]}] | Just (ExportSpecList _ xs) <- exports, EModuleContents a name `elem_` xs = let o2 = ModuleHead a name warning $ Just $ ExportSpecList a $ EVar a ellipses : delete_ (EModuleContents a name) xs in [ignore "Use explicit module export list" o o2 []] exportHint _ _ = [] hlint-2.0.11/src/Hint/Duplicate.hs0000644000000000000000000000516213210071537015041 0ustar0000000000000000{-# LANGUAGE PatternGuards, ScopedTypeVariables #-} {- Find bindings within a let, and lists of statements If you have n the same, error out main = do a; a; a; a main = do a; a; a; a; a; a -- ??? main = do a; a; a; a; a; a; a -- ??? main = do (do b; a; a; a); do (do c; a; a; a) -- ??? main = do a; a; a; b; a; a; a -- ??? main = do a; a; a; b; a; a foo = a where {a = 1; b = 2; c = 3}; bar = a where {a = 1; b = 2; c = 3} -- ??? -} module Hint.Duplicate(duplicateHint) where import Hint.Type import Data.Tuple.Extra import Data.List hiding (find) import qualified Data.Map as Map duplicateHint :: CrossHint duplicateHint ms = dupes [y | Do _ y :: Exp S <- universeBi modu] ++ dupes [y | BDecls l y :: Binds S <- universeBi modu] where modu = map snd ms dupes ys = [rawIdeaN (if length xs >= 5 then Warning else Suggestion) "Reduce duplication" p1 (unlines $ map (prettyPrint . fmap (const p1)) xs) (Just $ "Combine with " ++ showSrcLoc (getPointLoc p2)) [] | (p1,p2,xs) <- duplicateOrdered 3 $ map (map (srcInfoSpan . ann &&& dropAnn)) ys] --------------------------------------------------------------------- -- DUPLICATE FINDING -- | The position to return if we match at this point, and the map of where to go next -- If two runs have the same vals, always use the first pos you find data Dupe pos val = Dupe pos (Map.Map val (Dupe pos val)) find :: Ord val => [val] -> Dupe pos val -> (pos, Int) find (v:vs) (Dupe p mp) | Just d <- Map.lookup v mp = second (+1) $ find vs d find _ (Dupe p mp) = (p, 0) add :: Ord val => pos -> [val] -> Dupe pos val -> Dupe pos val add pos [] d = d add pos (v:vs) (Dupe p mp) = Dupe p $ Map.insertWith f v (add pos vs $ Dupe pos Map.empty) mp where f new = add pos vs duplicateOrdered :: Ord val => Int -> [[(SrcSpan,val)]] -> [(SrcSpan,SrcSpan,[val])] duplicateOrdered threshold xs = concat $ concat $ snd $ mapAccumL f (Dupe def Map.empty) xs where f d xs = second overlaps $ mapAccumL (g pos) d $ takeWhile ((>= threshold) . length) $ tails xs where pos = Map.fromList $ zip (map fst xs) [0..] g pos d xs = (d2, res) where res = [(p,pme,take mx vs) | i >= threshold ,let mx = maybe i (\x -> min i $ (pos Map.! pme) - x) $ Map.lookup p pos ,mx >= threshold] vs = map snd xs (p,i) = find vs d pme = fst $ head xs d2 = add pme vs d overlaps (x@((_,_,n):_):xs) = x : overlaps (drop (length n - 1) xs) overlaps (x:xs) = x : overlaps xs overlaps [] = [] hlint-2.0.11/src/Hint/Comment.hs0000644000000000000000000000223313210071537014525 0ustar0000000000000000 {- {- MISSING HASH #-} -- {-# MISSING HASH #-} {- INLINE X -} {- INLINE Y -} -- {-# INLINE Y #-} {- INLINE[~k] f -} -- {-# INLINE[~k] f #-} {- NOINLINE Y -} -- {-# NOINLINE Y #-} {- UNKNOWN Y -} INLINE X -} module Hint.Comment(commentHint) where import Hint.Type import Data.Char import Data.List.Extra import Refact.Types(Refactoring(ModifyComment)) pragmas = words $ "LANGUAGE OPTIONS_GHC INCLUDE WARNING DEPRECATED MINIMAL INLINE NOINLINE INLINABLE " ++ "CONLIKE LINE SPECIALIZE SPECIALISE UNPACK NOUNPACK SOURCE" commentHint :: Comment -> [Idea] commentHint c@(Comment True span s) | "#" `isSuffixOf` s && not ("#" `isPrefixOf` s) = [grab "Fix pragma markup" c $ '#':s] | name `elem` pragmas = [grab "Use pragma syntax" c $ "# " ++ trim s ++ " #"] where name = takeWhile (\x -> isAlphaNum x || x == '_') $ dropWhile isSpace s commentHint _ = [] grab :: String -> Comment -> String -> Idea grab msg (Comment typ pos s1) s2 = rawIdea Suggestion msg pos (f s1) (Just $ f s2) [] refact where f s = if typ then "{-" ++ s ++ "-}" else "--" ++ s refact = [ModifyComment (toRefactSrcSpan pos) (f s2)] hlint-2.0.11/src/Hint/Bracket.hs0000644000000000000000000001265413210071537014506 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} {- Raise an error if you are bracketing an atom, or are enclosed be a list bracket -- expression bracket reduction yes = (f x) x -- @Suggestion f x x no = f (x x) yes = (foo) -- foo yes = (foo bar) -- @Suggestion foo bar yes = foo (bar) -- @Warning bar yes = foo ((x x)) -- @Suggestion (x x) yes = (f x) ||| y -- @Suggestion f x ||| y yes = if (f x) then y else z -- @Suggestion if f x then y else z yes = if x then (f y) else z -- @Suggestion if x then f y else z yes = (a foo) :: Int -- @Suggestion a foo :: Int yes = [(foo bar)] -- @Suggestion [foo bar] yes = foo ((x y), z) -- @Suggestion (x y, z) yes = C { f = (e h) } -- @Suggestion C {f = e h} yes = \ x -> (x && x) -- @Suggestion \x -> x && x no = \(x -> y) -> z yes = (`foo` (bar baz)) -- @Suggestion (`foo` bar baz) yes = f ((x)) -- @Warning x main = do f; (print x) -- @Suggestion do f print x -- type bracket reduction foo :: (Int -> Int) -> Int foo :: (Maybe Int) -> a -- @Suggestion Maybe Int -> a instance Named (DeclHead S) data Foo = Foo {foo :: (Maybe Foo)} -- @Suggestion foo :: Maybe Foo -- pattern bracket reduction foo (x:xs) = 1 foo (True) = 1 -- @Warning True foo ((True)) = 1 -- @Warning True foo (A{}) = True -- A{} f x = case x of (Nothing) -> 1; _ -> 2 -- Nothing -- dollar reduction tests no = groupFsts . sortFst $ mr yes = split "to" $ names -- split "to" names yes = white $ keysymbol -- white keysymbol yes = operator foo $ operator -- operator foo operator no = operator foo $ operator bar yes = return $ Record{a=b} -- return Record{a=b} -- $/bracket rotation tests yes = (b $ c d) ++ e -- b (c d) ++ e yes = (a b $ c d) ++ e -- a b (c d) ++ e no = (f . g $ a) ++ e no = quickCheck ((\h -> cySucc h == succ h) :: Hygiene -> Bool) foo = (case x of y -> z; q -> w) :: Int -- backup fixity resolution main = do a += b . c; return $ a . b -- annotations main = 1; {-# ANN module ("HLint: ignore Use camelCase" :: String) #-} main = 1; {-# ANN module (1 + (2)) #-} -- 2 -- special cases (from esqueleto, see #224) main = operate <$> (select $ from $ \user -> return $ user ^. UserEmail) -} module Hint.Bracket(bracketHint) where import Hint.Type import Data.Data import Refact.Types bracketHint :: DeclHint bracketHint _ _ x = concatMap (\x -> bracket True x ++ dollar x) (childrenBi (descendBi annotations x) :: [Exp_]) ++ concatMap (bracket False) (childrenBi x :: [Type_]) ++ concatMap (bracket False) (childrenBi x :: [Pat_]) ++ concatMap fieldDecl (childrenBi x) where -- Brackets at the roots of annotations are fine, so we strip them annotations :: Annotation S -> Annotation S annotations = descendBi $ \x -> case (x :: Exp_) of Paren _ x -> x x -> x -- Dirty, should add to Brackets type class I think tyConToRtype :: String -> RType tyConToRtype "Exp" = Expr tyConToRtype "Type" = Type tyConToRtype "Pat" = Pattern tyConToRtype _ = Expr findType :: (Data a) => a -> RType findType = tyConToRtype . dataTypeName . dataTypeOf -- Just if at least one paren was removed -- Nothing if zero parens were removed remParens :: Brackets a => a -> Maybe a remParens = fmap go . remParen where go e = maybe e go (remParen e) bracket :: (Data (a S), ExactP a, Pretty (a S), Brackets (a S)) => Bool -> a S -> [Idea] bracket bad = f Nothing where msg = "Redundant bracket" -- f (Maybe (index, parent, gen)) child f :: (Data (a S), ExactP a, Pretty (a S), Brackets (a S)) => Maybe (Int,a S,a S -> a S) -> a S -> [Idea] f Just{} o@(remParens -> Just x) | isAtom x = bracketError msg o x : g x f Nothing o@(remParens -> Just x) | bad || isAtom x = (if isAtom x then bracketError else bracketWarning) msg o x : g x f (Just (i,o,gen)) v@(remParens -> Just x) | not $ needBracket i o x = suggest msg o (gen x) [r] : g x where typ = findType v r = Replace typ (toSS v) [("x", toSS x)] "x" f _ x = g x g :: (Data (a S), ExactP a, Pretty (a S), Brackets (a S)) => a S -> [Idea] g o = concat [f (Just (i,o,gen)) x | (i,(x,gen)) <- zip [0..] $ holes o] bracketWarning msg o x = suggest msg o x [Replace (findType x) (toSS o) [("x", toSS x)] "x"] bracketError msg o x = warn msg o x [Replace (findType x) (toSS o) [("x", toSS x)] "x"] fieldDecl :: FieldDecl S -> [Idea] fieldDecl o@(FieldDecl a b v@(TyParen _ c)) = [suggest "Redundant bracket" o (FieldDecl a b c) [Replace Type (toSS v) [("x", toSS c)] "x"]] fieldDecl _ = [] dollar :: Exp_ -> [Idea] dollar = concatMap f . universe where f x = [suggest "Redundant $" x y [r] | InfixApp _ a d b <- [x], opExp d ~= "$" ,let y = App an a b, not $ needBracket 0 y a, not $ needBracket 1 y b ,let r = Replace Expr (toSS x) [("a", toSS a), ("b", toSS b)] "a b" ] ++ [suggest "Move brackets to avoid $" x (t y) [r] |(t, e@(Paren _ (InfixApp _ a1 op1 a2))) <- splitInfix x ,opExp op1 ~= "$", isVar a1 || isApp a1 || isParen a1, not $ isAtom a2 ,not $ a1 ~= "select" -- special case for esqueleto, see #224 , let y = App an a1 (Paren an a2) , let r = Replace Expr (toSS e) [("a", toSS a1), ("b", toSS a2)] "a (b)" ] -- return both sides, and a way to put them together again splitInfix :: Exp_ -> [(Exp_ -> Exp_, Exp_)] splitInfix (InfixApp s a b c) = [(InfixApp s a b, c), (\a -> InfixApp s a b c, a)] splitInfix _ = [] hlint-2.0.11/src/Hint/All.hs0000644000000000000000000000456413210071537013644 0ustar0000000000000000 module Hint.All( Hint(..), HintBuiltin(..), DeclHint, ModuHint, resolveHints, hintRules, builtinHints ) where import Data.Monoid import Config.Type import Data.Either import Data.List import Hint.Type import Prelude import Hint.Match import Hint.List import Hint.ListRec import Hint.Monad import Hint.Lambda import Hint.Bracket import Hint.Naming import Hint.Pattern import Hint.Import import Hint.Export import Hint.Pragma import Hint.Restrict import Hint.Extensions import Hint.Duplicate import Hint.Comment import Hint.Unsafe import Hint.NewType -- | A list of the builtin hints wired into HLint. -- This list is likely to grow over time. data HintBuiltin = HintList | HintListRec | HintMonad | HintLambda | HintBracket | HintNaming | HintPattern | HintImport | HintExport | HintPragma | HintExtensions | HintUnsafe | HintDuplicate | HintRestrict | HintComment | HintNewType deriving (Show,Eq,Ord,Bounded,Enum) builtin :: HintBuiltin -> Hint builtin x = case x of HintList -> decl listHint HintListRec -> decl listRecHint HintMonad -> decl monadHint HintLambda -> decl lambdaHint HintBracket -> decl bracketHint HintNaming -> decl namingHint HintPattern -> decl patternHint HintImport -> modu importHint HintExport -> modu exportHint HintPragma -> modu pragmaHint HintExtensions -> modu extensionsHint HintUnsafe -> modu unsafeHint HintDuplicate -> mods duplicateHint HintComment -> comm commentHint HintNewType -> decl newtypeHint HintRestrict -> mempty{hintModule=restrictHint} where decl x = mempty{hintDecl=const x} modu x = mempty{hintModule=const x} mods x = mempty{hintModules=const x} comm x = mempty{hintComment=const x} -- | A list of builtin hints, currently including entries such as @\"List\"@ and @\"Bracket\"@. builtinHints :: [(String, Hint)] builtinHints = [(drop 4 $ show h, builtin h) | h <- [minBound .. maxBound]] -- | Transform a list of 'HintBuiltin' or 'HintRule' into a 'Hint'. resolveHints :: [Either HintBuiltin HintRule] -> Hint resolveHints xs = mconcat $ mempty{hintDecl=const $ readMatch rights} : map builtin (nub lefts) where (lefts,rights) = partitionEithers xs -- | Transform a list of 'HintRule' into a 'Hint'. hintRules :: [HintRule] -> Hint hintRules = resolveHints . map Right hlint-2.0.11/src/Config/0000755000000000000000000000000013210071537013072 5ustar0000000000000000hlint-2.0.11/src/Config/Yaml.hs0000644000000000000000000002624313210071537014337 0ustar0000000000000000{-# LANGUAGE OverloadedStrings, ViewPatterns, RecordWildCards, GeneralizedNewtypeDeriving, TupleSections #-} module Config.Yaml( ConfigYaml, readFileConfigYaml, settingsFromConfigYaml ) where import Config.Type import Data.Yaml import Data.Either import Data.Maybe import Data.List.Extra import Data.Tuple.Extra import Control.Monad.Extra import Control.Exception.Extra import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.ByteString.Char8 as BS import qualified Data.HashMap.Strict as Map import HSE.All hiding (Rule, String) import Data.Functor import Data.Monoid import Util import Prelude -- | Read a config file in YAML format. Takes a filename, and optionally the contents. -- Fails if the YAML doesn't parse or isn't valid HLint YAML readFileConfigYaml :: FilePath -> Maybe String -> IO ConfigYaml readFileConfigYaml file contents = do val <- case contents of Nothing -> decodeFileEither file Just src -> return $ decodeEither' $ BS.pack src case val of Left e -> fail $ "Failed to read YAML configuration file " ++ file ++ "\n " ++ displayException e Right v -> return v --------------------------------------------------------------------- -- YAML DATA TYPE newtype ConfigYaml = ConfigYaml [ConfigItem] deriving (Monoid,Show) data ConfigItem = ConfigPackage Package | ConfigGroup Group | ConfigSetting [Setting] deriving Show data Package = Package {packageName :: String ,packageModules :: [ImportDecl S] } deriving Show data Group = Group {groupName :: String ,groupEnabled :: Bool ,groupImports :: [Either String (ImportDecl S)] -- Left for package imports ,groupRules :: [Either HintRule Classify] -- HintRule has scope set to mempty } deriving Show --------------------------------------------------------------------- -- YAML PARSING LIBRARY data Val = Val Value -- the actual value I'm focused on [(String, Value)] -- the path of values I followed (for error messages) newVal :: Value -> Val newVal x = Val x [("root", x)] getVal :: Val -> Value getVal (Val x _) = x addVal :: String -> Value -> Val -> Val addVal key v (Val focus path) = Val v $ (key,v) : path -- | Failed when parsing some value, give an informative error message. parseFail :: Val -> String -> Parser a parseFail (Val focus path) msg = fail $ "Error when decoding YAML, " ++ msg ++ "\n" ++ "Along path: " ++ unwords steps ++ "\n" ++ "When at: " ++ fst (word1 $ show focus) ++ "\n" ++ -- aim to show a smallish but relevant context dotDot (fromMaybe (encode focus) $ listToMaybe $ dropWhile (\x -> BS.length x > 250) $ map encode contexts) where (steps, contexts) = unzip $ reverse path dotDot x = let (a,b) = BS.splitAt 250 x in BS.unpack a ++ (if BS.null b then "" else "...") parseArray :: Val -> Parser [Val] parseArray v@(getVal -> Array xs) = concatMapM parseArray $ zipWith (\i x -> addVal (show i) x v) [0..] $ V.toList xs parseArray v = return [v] parseObject :: Val -> Parser (Map.HashMap T.Text Value) parseObject (getVal -> Object x) = return x parseObject v = parseFail v "Expected an Object" parseObject1 :: Val -> Parser (String, Val) parseObject1 v = do mp <- parseObject v case Map.keys mp of [T.unpack -> s] -> (s,) <$> parseField s v _ -> parseFail v $ "Expected exactly one key but got " ++ show (Map.size mp) parseString :: Val -> Parser String parseString (getVal -> String x) = return $ T.unpack x parseString v = parseFail v "Expected a String" parseArrayString :: Val -> Parser [String] parseArrayString = parseArray >=> mapM parseString parseBool :: Val -> Parser Bool parseBool (getVal -> Bool b) = return b parseBool v = parseFail v "Expected a Bool" parseField :: String -> Val -> Parser Val parseField s v = do x <- parseFieldOpt s v case x of Nothing -> parseFail v $ "Expected a field named " ++ s Just v -> return v parseFieldOpt :: String -> Val -> Parser (Maybe Val) parseFieldOpt s v = do mp <- parseObject v case Map.lookup (T.pack s) mp of Nothing -> return Nothing Just x -> return $ Just $ addVal s x v allowFields :: Val -> [String] -> Parser () allowFields v allow = do mp <- parseObject v let bad = map T.unpack (Map.keys mp) \\ allow when (bad /= []) $ parseFail v $ "Not allowed keys: " ++ unwords bad parseHSE :: (ParseMode -> String -> ParseResult v) -> Val -> Parser v parseHSE parser v = do x <- parseString v case parser defaultParseMode{extensions=defaultExtensions} x of ParseOk x -> return x ParseFailed loc s -> parseFail v $ "Failed to parse " ++ s ++ ", when parsing:\n " ++ x --------------------------------------------------------------------- -- YAML TO DATA TYPE instance FromJSON ConfigYaml where parseJSON Null = return mempty parseJSON x = parseConfigYaml $ newVal x parseConfigYaml :: Val -> Parser ConfigYaml parseConfigYaml v = do vs <- parseArray v fmap ConfigYaml $ forM vs $ \o@v -> do (s, v) <- parseObject1 v case s of "package" -> ConfigPackage <$> parsePackage v "group" -> ConfigGroup <$> parseGroup v "arguments" -> ConfigSetting . map SettingArgument <$> parseArrayString v "fixity" -> ConfigSetting <$> parseFixity v _ | isJust $ getSeverity s -> ConfigGroup . ruleToGroup <$> parseRule o _ | Just r <- getRestrictType s -> ConfigSetting . map SettingRestrict <$> (parseArray v >>= mapM (parseRestrict r)) _ -> parseFail v "Expecting an object with a 'package' or 'group' key, a hint or a restriction" parsePackage :: Val -> Parser Package parsePackage v = do packageName <- parseField "name" v >>= parseString packageModules <- parseField "modules" v >>= parseArray >>= mapM (parseHSE parseImportDeclWithMode) allowFields v ["name","modules"] return Package{..} parseFixity :: Val -> Parser [Setting] parseFixity v = parseArray v >>= concatMapM (parseHSE parseDeclWithMode >=> f) where f x@InfixDecl{} = return $ map Infix $ getFixity x f _ = parseFail v "Expected fixity declaration" parseGroup :: Val -> Parser Group parseGroup v = do groupName <- parseField "name" v >>= parseString groupEnabled <- parseFieldOpt "enabled" v >>= maybe (return True) parseBool groupImports <- parseFieldOpt "imports" v >>= maybe (return []) (parseArray >=> mapM parseImport) groupRules <- parseFieldOpt "rules" v >>= maybe (return []) parseArray >>= concatMapM parseRule allowFields v ["name","enabled","imports","rules"] return Group{..} where parseImport v = do x <- parseString v case word1 x of ("package", x) -> return $ Left x _ -> Right <$> parseHSE parseImportDeclWithMode v ruleToGroup :: [Either HintRule Classify] -> Group ruleToGroup = Group "" True [] parseRule :: Val -> Parser [Either HintRule Classify] parseRule v = do (severity, v) <- parseSeverityKey v isRule <- isJust <$> parseFieldOpt "lhs" v if isRule then do hintRuleLHS <- parseField "lhs" v >>= parseHSE parseExpWithMode hintRuleRHS <- parseField "rhs" v >>= parseHSE parseExpWithMode hintRuleNotes <- parseFieldOpt "note" v >>= maybe (return []) (fmap (map asNote) . parseArrayString) hintRuleName <- parseFieldOpt "name" v >>= maybe (return $ guessName hintRuleLHS hintRuleRHS) parseString hintRuleSide <- parseFieldOpt "side" v >>= maybe (return Nothing) (fmap Just . parseHSE parseExpWithMode) allowFields v ["lhs","rhs","note","name","side"] let hintRuleScope = mempty return [Left HintRule{hintRuleSeverity=severity, ..}] else do names <- parseFieldOpt "name" v >>= maybe (return []) parseArrayString within <- parseFieldOpt "within" v >>= maybe (return [("","")]) (parseArray >=> concatMapM parseWithin) return [Right $ Classify severity n a b | (a,b) <- within, n <- ["" | null names] ++ names] parseRestrict :: RestrictType -> Val -> Parser Restrict parseRestrict restrictType v = do def <- parseFieldOpt "default" v case def of Just def -> do b <- parseBool def allowFields v ["default"] return $ Restrict restrictType b [] [] [] Nothing -> do restrictName <- parseFieldOpt "name" v >>= maybe (return []) parseArrayString restrictWithin <- parseFieldOpt "within" v >>= maybe (return [("","")]) (parseArray >=> concatMapM parseWithin) restrictAs <- parseFieldOpt "as" v >>= maybe (return []) parseArrayString allowFields v $ ["as" | restrictType == RestrictModule] ++ ["name","within"] return Restrict{restrictDefault=True,..} parseWithin :: Val -> Parser [(String, String)] -- (module, decl) parseWithin v = do x <- parseHSE parseExpWithMode v case x of Var _ (UnQual _ name) -> return [("",fromNamed name)] Var _ (Qual _ (ModuleName _ mod) name) -> return [(mod, fromNamed name)] Con _ (UnQual _ name) -> return [(fromNamed name,""),("",fromNamed name)] Con _ (Qual _ (ModuleName _ mod) name) -> return [(mod ++ "." ++ fromNamed name,""),(mod,fromNamed name)] _ -> parseFail v "Bad classification rule" parseSeverityKey :: Val -> Parser (Severity, Val) parseSeverityKey v = do (s, v) <- parseObject1 v case getSeverity s of Just sev -> return (sev, v) _ -> parseFail v $ "Key should be a severity (e.g. warn/error/suggest) but got " ++ s guessName :: Exp_ -> Exp_ -> String guessName lhs rhs | n:_ <- rs \\ ls = "Use " ++ n | n:_ <- ls \\ rs = "Redundant " ++ n | otherwise = defaultHintName where (ls, rs) = both f (lhs, rhs) f = filter (not . isUnifyVar) . map (\x -> fromNamed (x :: Name S)) . childrenS asNote :: String -> Note asNote "IncreasesLaziness" = IncreasesLaziness asNote "DecreasesLaziness" = DecreasesLaziness asNote (word1 -> ("RemovesError",x)) = RemovesError x asNote (word1 -> ("ValidInstance",x)) = uncurry ValidInstance $ word1 x asNote x = Note x --------------------------------------------------------------------- -- SETTINGS settingsFromConfigYaml :: [ConfigYaml] -> [Setting] settingsFromConfigYaml (mconcat -> ConfigYaml configs) = settings ++ concatMap f groups where packages = [x | ConfigPackage x <- configs] groups = [x | ConfigGroup x <- configs] settings = concat [x | ConfigSetting x <- configs] packageMap = Map.fromListWith (++) [(packageName, packageModules) | Package{..} <- packages] groupMap = Map.fromListWith (\new old -> new) [(groupName, groupEnabled) | Group{..} <- groups] f Group{..} | Map.lookup groupName groupMap == Just False = [] | otherwise = map (either (\r -> SettingMatchExp r{hintRuleScope=scope}) SettingClassify) groupRules where scope = asScope packageMap groupImports asScope :: Map.HashMap String [ImportDecl S] -> [Either String (ImportDecl S)] -> Scope asScope packages xs = scopeCreate $ Module an Nothing [] (concatMap f xs) [] where f (Right x) = [x] f (Left x) | Just pkg <- Map.lookup x packages = pkg | otherwise = error $ "asScope failed to do lookup, " ++ x hlint-2.0.11/src/Config/Type.hs0000644000000000000000000001077513210071537014361 0ustar0000000000000000 module Config.Type( Severity(..), Classify(..), HintRule(..), Note(..), Setting(..), Restrict(..), RestrictType(..), defaultHintName, isUnifyVar, showNotes, getSeverity, getRestrictType ) where import HSE.All import Data.Char import Data.List.Extra import Prelude getSeverity :: String -> Maybe Severity getSeverity "ignore" = Just Ignore getSeverity "warn" = Just Warning getSeverity "warning" = Just Warning getSeverity "suggest" = Just Suggestion getSeverity "suggestion" = Just Suggestion getSeverity "error" = Just Error getSeverity "hint" = Just Suggestion getSeverity _ = Nothing getRestrictType :: String -> Maybe RestrictType getRestrictType "modules" = Just RestrictModule getRestrictType "extensions" = Just RestrictExtension getRestrictType "flags" = Just RestrictFlag getRestrictType "functions" = Just RestrictFunction getRestrictType _ = Nothing defaultHintName :: String defaultHintName = "Use alternative" -- | How severe an issue is. data Severity = Ignore -- ^ The issue has been explicitly ignored and will usually be hidden (pass @--show@ on the command line to see ignored ideas). | Suggestion -- ^ Suggestions are things that some people may consider improvements, but some may not. | Warning -- ^ Warnings are suggestions that are nearly always a good idea to apply. | Error -- ^ Available as a setting for the user. deriving (Eq,Ord,Show,Read,Bounded,Enum) -- Any 1-letter variable names are assumed to be unification variables isUnifyVar :: String -> Bool isUnifyVar [x] = x == '?' || isAlpha x isUnifyVar _ = False --------------------------------------------------------------------- -- TYPE -- | A note describing the impact of the replacement. data Note = IncreasesLaziness -- ^ The replacement is increases laziness, for example replacing @reverse (reverse x)@ with @x@ makes the code lazier. | DecreasesLaziness -- ^ The replacement is decreases laziness, for example replacing @(fst x, snd x)@ with @x@ makes the code stricter. | RemovesError String -- ^ The replacement removes errors, for example replacing @foldr1 (+)@ with @sum@ removes an error on @[]@, and might contain the text @\"on []\"@. | ValidInstance String String -- ^ The replacement assumes standard type class lemmas, a hint with the note @ValidInstance \"Eq\" \"x\"@ might only be valid if -- the @x@ variable has a reflexive @Eq@ instance. | Note String -- ^ An arbitrary note. deriving (Eq,Ord) instance Show Note where show IncreasesLaziness = "increases laziness" show DecreasesLaziness = "decreases laziness" show (RemovesError x) = "removes error " ++ x show (ValidInstance x y) = "requires a valid `" ++ x ++ "` instance for `" ++ y ++ "`" show (Note x) = x showNotes :: [Note] -> String showNotes = intercalate ", " . map show . filter use where use ValidInstance{} = False -- Not important enough to tell an end user use _ = True -- | How to classify an 'Idea'. If any matching field is @\"\"@ then it matches everything. data Classify = Classify {classifySeverity :: Severity -- ^ Severity to set the 'Idea' to. ,classifyHint :: String -- ^ Match on 'Idea' field 'ideaHint'. ,classifyModule :: String -- ^ Match on 'Idea' field 'ideaModule'. ,classifyDecl :: String -- ^ Match on 'Idea' field 'ideaDecl'. } deriving Show -- | A @LHS ==> RHS@ style hint rule. data HintRule {- PUBLIC -} = HintRule {hintRuleSeverity :: Severity -- ^ Default severity for the hint. ,hintRuleName :: String -- ^ Name for the hint. ,hintRuleScope :: Scope -- ^ Module scope in which the hint operates. ,hintRuleLHS :: Exp SrcSpanInfo -- ^ LHS ,hintRuleRHS :: Exp SrcSpanInfo -- ^ RHS ,hintRuleSide :: Maybe (Exp SrcSpanInfo) -- ^ Side condition, typically specified with @where _ = ...@. ,hintRuleNotes :: [Note] -- ^ Notes about application of the hint. } deriving Show data RestrictType = RestrictModule | RestrictExtension | RestrictFlag | RestrictFunction deriving (Show,Eq,Ord) data Restrict = Restrict {restrictType :: RestrictType ,restrictDefault :: Bool ,restrictName :: [String] ,restrictAs :: [String] -- for RestrictModule only, what you can import it as ,restrictWithin :: [(String, String)] } deriving Show data Setting = SettingClassify Classify | SettingMatchExp HintRule | SettingRestrict Restrict | SettingArgument String -- ^ Extra command-line argument | Builtin String -- use a builtin hint set | Infix Fixity deriving Show hlint-2.0.11/src/Config/Read.hs0000644000000000000000000000102713210071537014301 0ustar0000000000000000 module Config.Read(readFilesConfig) where import Config.Type import Config.Haskell import Config.Yaml import Data.List.Extra import System.FilePath readFilesConfig :: [(FilePath, Maybe String)] -> IO [Setting] readFilesConfig files = do yaml <- mapM (uncurry readFileConfigYaml) yaml haskell <- mapM (uncurry readFileConfigHaskell) haskell return $ concat haskell ++ settingsFromConfigYaml yaml where (yaml, haskell) = partition (\(x,_) -> lower (takeExtension x) `elem` [".yml",".yaml"]) files hlint-2.0.11/src/Config/Haskell.hs0000644000000000000000000001267613210071537015025 0ustar0000000000000000{-# LANGUAGE PatternGuards, ViewPatterns #-} module Config.Haskell( readPragma, readSetting, readFileConfigHaskell ) where import HSE.All import Data.Char import Data.List.Extra import Config.Type import Util import Prelude addInfix = parseFlagsAddFixities $ infix_ (-1) ["==>"] --------------------------------------------------------------------- -- READ A SETTINGS FILE readFileConfigHaskell :: FilePath -> Maybe String -> IO [Setting] readFileConfigHaskell file contents = do let flags = addInfix defaultParseFlags res <- parseModuleEx flags file contents case res of Left (ParseError sl msg err) -> error $ "Config parse failure at " ++ showSrcLoc sl ++ ": " ++ msg ++ "\n" ++ err Right (m, _) -> return $ readSettings m -- | Given a module containing HLint settings information return the 'Classify' rules and the 'HintRule' expressions. -- Any fixity declarations will be discarded, but any other unrecognised elements will result in an exception. readSettings :: Module_ -> [Setting] readSettings m = concatMap (readSetting $ scopeCreate m) $ concatMap getEquations $ [AnnPragma l x | AnnModulePragma l x <- modulePragmas m] ++ moduleDecls m readSetting :: Scope -> Decl_ -> [Setting] readSetting s (FunBind _ [Match _ (Ident _ (getSeverity -> Just severity)) pats (UnGuardedRhs _ bod) bind]) | InfixApp _ lhs op rhs <- bod, opExp op ~= "==>" = let (a,b) = readSide $ childrenBi bind in [SettingMatchExp $ HintRule severity (head $ snoc names defaultHintName) s (fromParen lhs) (fromParen rhs) a b] | otherwise = [SettingClassify $ Classify severity n a b | n <- names2, (a,b) <- readFuncs bod] where names = filter (not . null) $ getNames pats bod names2 = ["" | null names] ++ names readSetting s x | "test" `isPrefixOf` map toLower (fromNamed x) = [] readSetting s (AnnPragma _ x) | Just y <- readPragma x = [SettingClassify y] readSetting s (PatBind an (PVar _ name) bod bind) = readSetting s $ FunBind an [Match an name [] bod bind] readSetting s (FunBind an xs) | length xs /= 1 = concatMap (readSetting s . FunBind an . return) xs readSetting s (SpliceDecl an (App _ (Var _ x) (Lit _ y))) = readSetting s $ FunBind an [Match an (toNamed $ fromNamed x) [PLit an (Signless an) y] (UnGuardedRhs an $ Lit an $ String an "" "") Nothing] readSetting s x@InfixDecl{} = map Infix $ getFixity x readSetting s x = errorOn x "bad hint" -- | Read an {-# ANN #-} pragma and determine if it is intended for HLint. -- Return Nothing if it is not an HLint pragma, otherwise what it means. readPragma :: Annotation S -> Maybe Classify readPragma o = case o of Ann _ name x -> f (fromNamed name) x TypeAnn _ name x -> f (fromNamed name) x ModuleAnn _ x -> f "" x where f name (Lit _ (String _ s _)) | "hlint:" `isPrefixOf` map toLower s = case getSeverity a of Nothing -> errorOn o "bad classify pragma" Just severity -> Just $ Classify severity (trimStart b) "" name where (a,b) = break isSpace $ trimStart $ drop 6 s f name (Paren _ x) = f name x f name (ExpTypeSig _ x _) = f name x f _ _ = Nothing readSide :: [Decl_] -> (Maybe Exp_, [Note]) readSide = foldl f (Nothing,[]) where f (Nothing,notes) (PatBind _ PWildCard{} (UnGuardedRhs _ side) Nothing) = (Just side, notes) f (Nothing,notes) (PatBind _ (fromNamed -> "side") (UnGuardedRhs _ side) Nothing) = (Just side, notes) f (side,[]) (PatBind _ (fromNamed -> "note") (UnGuardedRhs _ note) Nothing) = (side,g note) f _ x = errorOn x "bad side condition" g (Lit _ (String _ x _)) = [Note x] g (List _ xs) = concatMap g xs g x = case fromApps x of [con -> Just "IncreasesLaziness"] -> [IncreasesLaziness] [con -> Just "DecreasesLaziness"] -> [DecreasesLaziness] [con -> Just "RemovesError",fromString -> Just a] -> [RemovesError a] [con -> Just "ValidInstance",fromString -> Just a,var -> Just b] -> [ValidInstance a b] _ -> errorOn x "bad note" con :: Exp_ -> Maybe String con c@Con{} = Just $ prettyPrint c; con _ = Nothing var c@Var{} = Just $ prettyPrint c; var _ = Nothing -- Note: Foo may be ("","Foo") or ("Foo",""), return both readFuncs :: Exp_ -> [(String, String)] readFuncs (App _ x y) = readFuncs x ++ readFuncs y readFuncs (Lit _ (String _ "" _)) = [("","")] readFuncs (Var _ (UnQual _ name)) = [("",fromNamed name)] readFuncs (Var _ (Qual _ (ModuleName _ mod) name)) = [(mod, fromNamed name)] readFuncs (Con _ (UnQual _ name)) = [(fromNamed name,""),("",fromNamed name)] readFuncs (Con _ (Qual _ (ModuleName _ mod) name)) = [(mod ++ "." ++ fromNamed name,""),(mod,fromNamed name)] readFuncs x = errorOn x "bad classification rule" getNames :: [Pat_] -> Exp_ -> [String] getNames ps _ | ps /= [], Just ps <- mapM fromPString ps = ps getNames [] (InfixApp _ lhs op rhs) | opExp op ~= "==>" = map ("Use "++) names where lnames = map f $ childrenS lhs rnames = map f $ childrenS rhs names = filter (not . isUnifyVar) $ (rnames \\ lnames) ++ rnames f (Ident _ x) = x f (Symbol _ x) = x getNames _ _ = [] errorOn :: (Annotated ast, Pretty (ast S)) => ast S -> String -> b errorOn val msg = exitMessageImpure $ showSrcLoc (getPointLoc $ ann val) ++ ": Error while reading hint file, " ++ msg ++ "\n" ++ prettyPrint val hlint-2.0.11/src/Config/Compute.hs0000644000000000000000000000540513210071537015046 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} -- | Given a file, guess settings from it by looking at the hints. module Config.Compute(computeSettings) where import HSE.All import Config.Type import Config.Haskell import Data.Monoid import Prelude -- | Given a source file, guess some hints that might apply. -- Returns the text of the hints (if you want to save it down) along with the settings to be used. computeSettings :: ParseFlags -> FilePath -> IO (String, [Setting]) computeSettings flags file = do x <- parseModuleEx flags file Nothing case x of Left (ParseError sl msg _) -> return ("# Parse error " ++ showSrcLoc sl ++ ": " ++ msg, []) Right (m, _) -> do let xs = concatMap (findSetting $ UnQual an) (moduleDecls m) r = concatMap (readSetting mempty) xs s = unlines $ ["# hints found in " ++ file] ++ concatMap renderSetting r ++ ["# no hints found" | null xs] return (s,r) renderSetting :: Setting -> [String] renderSetting (SettingMatchExp HintRule{..}) = ["- warn: {lhs: " ++ show (prettyPrint hintRuleLHS) ++ ", rhs: " ++ show (prettyPrint hintRuleRHS) ++ "}"] renderSetting (Infix x) = ["- infix: " ++ show (prettyPrint (toInfixDecl x))] renderSetting _ = [] findSetting :: (Name S -> QName S) -> Decl_ -> [Decl_] findSetting qual (InstDecl _ _ _ (Just xs)) = concatMap (findSetting qual) [x | InsDecl _ x <- xs] findSetting qual (PatBind _ (PVar _ name) (UnGuardedRhs _ bod) Nothing) = findExp (qual name) [] bod findSetting qual (FunBind _ [InfixMatch _ p1 name ps rhs bind]) = findSetting qual $ FunBind an [Match an name (p1:ps) rhs bind] findSetting qual (FunBind _ [Match _ name ps (UnGuardedRhs _ bod) Nothing]) = findExp (qual name) [] $ Lambda an ps bod findSetting _ x@InfixDecl{} = [x] findSetting _ _ = [] -- given a result function name, a list of variables, a body expression, give some hints findExp :: QName S -> [String] -> Exp_ -> [Decl_] findExp name vs (Lambda _ ps bod) | length ps2 == length ps = findExp name (vs++ps2) bod | otherwise = [] where ps2 = [x | PVar_ x <- map view ps] findExp name vs Var{} = [] findExp name vs (InfixApp _ x dot y) | isDot dot = findExp name (vs++["_hlint"]) $ App an x $ Paren an $ App an y (toNamed "_hlint") findExp name vs bod = [PatBind an (toNamed "warn") (UnGuardedRhs an $ InfixApp an lhs (toNamed "==>") rhs) Nothing] where lhs = g $ transform f bod rhs = apps $ Var an name : map snd rep rep = zip vs $ map (toNamed . return) ['a'..] f xx | Var_ x <- view xx, Just y <- lookup x rep = y f (InfixApp _ x dol y) | isDol dol = App an x (paren y) f x = x g o@(InfixApp _ _ _ x) | isAnyApp x || isAtom x = o g o@App{} = o g o = paren o hlint-2.0.11/data/0000755000000000000000000000000013210071537012007 5ustar0000000000000000hlint-2.0.11/data/Test.hs0000644000000000000000000000554313210071537013271 0ustar0000000000000000-- These hints are for test purposes, and are not intended to -- be used for real. -- FIXME: Should make this module modules in one file, so can easily test lots of -- things without them overlapping module HLint.Test where error = Prelude.readFile ==> bad error = (x :: Int) ==> (x :: Int32) where _ = noTypeCheck error "Test1" = scanr ==> scanr error "Test2" = filter ==> filter error "Test3" = foldr ==> foldr error "Test4" = foldl ==> foldl ignore "Test1" = "" ignore "Test3" ignore "Test2" = ignoreTest warn = ignoreTest3 suggest = ignoreTest4 ignore = Ignore_Test {-# ANN module "HLint: ignore Test4" #-} {-# ANN annTest2 "HLint: error" #-} {-# ANN annTest3 ("HLint: warn" :: String) #-} {-# ANN annTest4 ("HLint: suggest" :: String) #-} {-# ANN type Ann_Test ("HLint: ignore") #-} error = concat (map f x) ==> Data.List.concatMap f x infix 9 + error = a * (b+c) ==> undefined error = Array.head ==> head error = tail ==> Array.tail warn = id Control.Arrow.*** id ==> id error = zip [1..length x] x ==> zipFrom 1 x error = before a ==> after a {- main = readFile "foo" >>= putStr \ -- bad import Prelude hiding(readFile) \ import Data.ByteString.Char8(readFile) \ test = readFile "foo" >>= putStr import Prelude as Prelude2 \ yes = Prelude2.readFile "foo" >>= putStr \ -- bad yes = 32 :: Int -- 32 :: Int32 yes = before 12 -- after 12 ignoreTest = filter -- @Ignore ??? ignoreTest2 = filter -- @Error ??? ignoreTest3 = filter -- @Warning ??? ignoreTest4 = filter -- @Suggestion ??? ignoreAny = scanr -- @Ignore ??? ignoreNew = foldr -- @Ignore ??? type Ignore_Test = Int -- @Ignore ??? annTest = foldl -- @Ignore ??? annTest2 = foldl -- @Error ??? annTest3 = scanr -- @Warning ??? annTest4 = scanr -- @Suggestion ??? type Ann_Test = Int -- @Ignore ??? concatMap f x = concat (map f x) concatMop f x = concat (map f x) -- Data.List.concatMap f x yes = 1 * 2+3 -- undefined import Foo; test = Foo.id 1 test = head import Array; test = Array.head -- head test = Array.head -- head test = head import qualified Array; test = head import Array(tail); test = head import Array(head); test = head -- head import Array as A; test = A.head -- head test = tail -- Array.tail import qualified Array as B; test = tail -- B.tail import Control.Arrow; test = id *** id -- id test = id Control.Arrow.*** id -- id import Control.Arrow as Q; test = id Q.*** id -- id zip [1..length x] zip [1..length x] x -- zipFrom 1 x {-# ANN module "HLint: ignore Unused LANGUAGE pragma" #-} \ {-# LANGUAGE RecordWildCards #-} -- @Ignore ??? {-# ANN module "HLint: ignore Unused LANGUAGE pragma" #-} \ {-# LANGUAGE RecordWildCards #-} -- @Ignore ??? {-# ANN lam "HLint: ignore Redundant lambda" #-} \ lam = \x -> x x x -- @Ignore ??? {-# ANN module "HLint: ignore Reduce duplication" #-} \ dup = do a; a; a; a; a; a -- @Ignore ??? -} hlint-2.0.11/data/report_template.html0000644000000000000000000000656713210071537016121 0ustar0000000000000000 HLint Report

    All hints

      $HINTS

    All files

      $FILES

    Report generated by HLint $VERSION - a tool to suggest improvements to your Haskell code.

    $CONTENT
    hlint-2.0.11/data/hs-lint.el0000644000000000000000000000762313210071537013717 0ustar0000000000000000;;; hs-lint.el --- minor mode for HLint code checking ;; Copyright 2009 (C) Alex Ott ;; ;; Author: Alex Ott ;; Keywords: haskell, lint, HLint ;; Requirements: ;; Status: distributed under terms of GPL2 or above ;; Typical message from HLint looks like: ;; ;; /Users/ott/projects/lang-exp/haskell/test.hs:52:1: Eta reduce ;; Found: ;; count1 p l = length (filter p l) ;; Why not: ;; count1 p = length . filter p (require 'compile) (defgroup hs-lint nil "Run HLint as inferior of Emacs, parse error messages." :group 'tools :group 'haskell) (defcustom hs-lint-command "hlint" "The default hs-lint command for \\[hlint]." :type 'string :group 'hs-lint) (defcustom hs-lint-save-files t "Save modified files when run HLint or no (ask user)" :type 'boolean :group 'hs-lint) (defcustom hs-lint-replace-with-suggestions nil "Replace user's code with suggested replacements" :type 'boolean :group 'hs-lint) (defcustom hs-lint-replace-without-ask nil "Replace user's code with suggested replacements automatically" :type 'boolean :group 'hs-lint) (defun hs-lint-process-setup () "Setup compilation variables and buffer for `hlint'." (run-hooks 'hs-lint-setup-hook)) ;; regex for replace suggestions ;; ;; ^\(.*?\):\([0-9]+\):\([0-9]+\): .* ;; Found: ;; \s +\(.*\) ;; Why not: ;; \s +\(.*\) (defvar hs-lint-regex "^\\(.*?\\):\\([0-9]+\\):\\([0-9]+\\): .*[\n\C-m]Found:[\n\C-m]\\s +\\(.*\\)[\n\C-m]Why not:[\n\C-m]\\s +\\(.*\\)[\n\C-m]" "Regex for HLint messages") (defun make-short-string (str maxlen) (if (< (length str) maxlen) str (concat (substring str 0 (- maxlen 3)) "..."))) (defun hs-lint-replace-suggestions () "Perform actual replacement of suggestions" (goto-char (point-min)) (while (re-search-forward hs-lint-regex nil t) (let* ((fname (match-string 1)) (fline (string-to-number (match-string 2))) (old-code (match-string 4)) (new-code (match-string 5)) (msg (concat "Replace '" (make-short-string old-code 30) "' with '" (make-short-string new-code 30) "'")) (bline 0) (eline 0) (spos 0) (new-old-code "")) (save-excursion (switch-to-buffer (get-file-buffer fname)) (goto-line fline) (beginning-of-line) (setf bline (point)) (when (or hs-lint-replace-without-ask (yes-or-no-p msg)) (end-of-line) (setf eline (point)) (beginning-of-line) (setf old-code (regexp-quote old-code)) (while (string-match "\\\\ " old-code spos) (setf new-old-code (concat new-old-code (substring old-code spos (match-beginning 0)) "\\ *")) (setf spos (match-end 0))) (setf new-old-code (concat new-old-code (substring old-code spos))) (remove-text-properties bline eline '(composition nil)) (when (re-search-forward new-old-code eline t) (replace-match new-code nil t))))))) (defun hs-lint-finish-hook (buf msg) "Function, that is executed at the end of HLint execution" (if hs-lint-replace-with-suggestions (hs-lint-replace-suggestions) (next-error 1 t))) (define-compilation-mode hs-lint-mode "HLint" "Mode for check Haskell source code." (set (make-local-variable 'compilation-process-setup-function) 'hs-lint-process-setup) (set (make-local-variable 'compilation-disable-input) t) (set (make-local-variable 'compilation-scroll-output) nil) (set (make-local-variable 'compilation-finish-functions) (list 'hs-lint-finish-hook)) ) (defun hs-lint () "Run HLint for current buffer with haskell source" (interactive) (save-some-buffers hs-lint-save-files) (compilation-start (concat hs-lint-command " \"" buffer-file-name "\"") 'hs-lint-mode)) (provide 'hs-lint) ;;; hs-lint.el ends here hlint-2.0.11/data/HLint_TypeCheck.hs0000644000000000000000000000050513210071537015320 0ustar0000000000000000 -- Used with --typecheck module HLint_TypeCheck where (==>) :: a -> a -> a (==>) = undefined _noParen_ = id _eval_ = id --------------------------------------------------------------------- -- EXAMPLES main :: IO () main = return () {-# LINE 116 "data\\Default.hs" #-} _test64 = \ p x -> (and (map p x)) ==> (all p x) hlint-2.0.11/data/HLint_QuickCheck.hs0000644000000000000000000001125013210071537015452 0ustar0000000000000000{-# LANGUAGE NoMonomorphismRestriction, ExtendedDefaultRules, ScopedTypeVariables, DeriveDataTypeable, ViewPatterns #-} {-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances, GeneralizedNewtypeDeriving #-} {-# LANGUAGE StandaloneDeriving #-} -- | Used with --quickcheck module HLint_QuickCheck(module HLint_QuickCheck, module X) where import System.IO.Unsafe import Data.Typeable import Data.List import Data.Maybe import Data.IORef import Control.Exception import Control.Monad import System.IO import Control.Concurrent.Chan import System.Mem.Weak(Weak) import Test.QuickCheck hiding ((==>)) import Test.QuickCheck.Test hiding (test) import Test.QuickCheck.Modifiers as X default(Maybe Bool,[Bool],Int,Dbl) -- We need a Show instance that nails down the sides, so defaulting works. -- The one from Text.Show.Functions is insufficient. instance (Show a, Show b) => Show (a -> b) where show _ = "" newtype Dbl = Dbl Double deriving (Enum,Floating,Fractional,Num,Read,Real,RealFloat,RealFrac,Show,Typeable,Arbitrary,CoArbitrary) instance Eq Dbl where Dbl a == Dbl b | isNaN a && isNaN b = True | otherwise = abs (a - b) < 1e-4 || let s = a+b in s /= 0 && abs ((a-b)/s) < 1e-8 instance Ord Dbl where compare a b | a == b = EQ compare (Dbl a) (Dbl b) = compare a b newtype NegZero a = NegZero a deriving (Typeable, Show) instance (Num a, Arbitrary a) => Arbitrary (NegZero a) where arbitrary = fmap (NegZero . negate . abs) arbitrary newtype Nat a = Nat a deriving (Typeable, Show) instance (Num a, Arbitrary a) => Arbitrary (Nat a) where arbitrary = fmap (Nat . abs) arbitrary newtype Compare a = Compare (a -> a -> Ordering) deriving (Typeable, Show) instance (Ord a, Arbitrary a) => Arbitrary (Compare a) where arbitrary = fmap (\b -> Compare $ (if b then flip else id) compare) arbitrary instance Show a => Show (IO a) where show _ = "" instance Show a => Show (Weak a) where show _ = "" instance Show a => Show (Chan a) where show _ = "" instance Eq (IO a) where _ == _ = True instance Eq SomeException where a == b = show a == show b deriving instance Typeable IOMode instance Arbitrary Handle where arbitrary = elements [stdin, stdout, stderr] instance CoArbitrary Handle where coarbitrary _ = variant 0 instance Arbitrary IOMode where arbitrary = elements [ReadMode,WriteMode,AppendMode,ReadWriteMode] instance Arbitrary a => Arbitrary (IO a) where arbitrary = fmap return arbitrary instance Arbitrary (Chan a) where arbitrary = return $ unsafePerformIO newChan instance Exception (Maybe Bool) data Test a = Test Bool a a deriving (Show, Typeable) instance Functor Test where fmap f (Test a b c) = Test a (f b) (f c) a ==> b = Test False a b a ?==> b = Test True a b class Testable2 a where property2 :: Test a -> Property instance Testable2 a => Testable (Test a) where property = property2 instance Eq a => Testable2 a where property2 (Test bx (catcher -> x) (catcher -> y)) = property $ (bx && isNothing x) || x == y instance (Arbitrary a, Show a, Testable2 b) => Testable2 (a -> b) where property2 x = property $ \a -> fmap ($ a) x {-# NOINLINE bad #-} bad :: IORef Int bad = unsafePerformIO $ newIORef 0 test :: (Show p, Testable p, Typeable p) => FilePath -> Int -> String -> p -> IO () test file line hint p = do res <- quickCheckWithResult stdArgs{chatty=False} p unless (isSuccess res) $ do putStrLn $ "\n" ++ file ++ ":" ++ show line ++ ": " ++ hint print $ typeOf p putStr $ output res modifyIORef bad (+1) catcher :: a -> Maybe a catcher x = unsafePerformIO $ do res <- try $ evaluate x return $ case res of Left (_ :: SomeException) -> Nothing Right v -> Just v _noParen_ = id _eval_ = id withMain :: IO () -> IO () withMain act = do act bad <- readIORef bad when (bad > 0) $ error $ "Failed " ++ show bad ++ " tests" --------------------------------------------------------------------- -- EXAMPLES main :: IO () main = withMain $ do let t = \ a -> (findIndex ((==) a)) ==> (elemIndex a) in test "data\\Default.hs" 144 "findIndex ((==) a) ==> elemIndex a" t let t = ((foldr1 (&&)) ?==> (and)) in test "data\\Default.hs" 179 "foldr1 (&&) ==> and" t let t = \ x -> (sqrt x) ==> (x ** 0.5) in test "data\\Default.hs" 407 "sinh x / cosh x ==> tanh x" t let t = \ (NegZero i) x -> (take i x) ==> ([]) in test "data\\Default.hs" 154 "take i x ==> []" t let t = \ (Compare f) x -> (head (sortBy f x)) ==> (minimumBy f x) in test "data\\Default.hs" 70 "head (sortBy f x) ==> minimumBy f x" t let t = \ f -> ((f $)) ==> (f) in test "data\\Default.hs" 218 "(f $) ==> f" t hlint-2.0.11/data/hlint.yaml0000644000000000000000000011356413210071537014023 0ustar0000000000000000# hlint configuration file # ================================== # The hlint tool is mainly automatic, but some hints/restrictions can be specified here. - package: name: base modules: - import Prelude - import Control.Arrow - import Control.Exception - import Control.Monad - import Control.Monad.Trans.State - import qualified Data.Foldable - import Data.Foldable(asum, sequenceA_, traverse_, for_) - import Data.Traversable(traverse, for) - import Control.Applicative - import Data.Function - import Data.Int - import Data.Char - import Data.List as Data.List - import Data.List as X - import Data.Maybe - import Data.Monoid - import System.IO - import Control.Concurrent.Chan - import System.Mem.Weak - import Control.Exception.Base - import System.Exit - import Data.Either - import Numeric - import IO as System.IO - import List as Data.List - import Maybe as Data.Maybe - import Monad as Control.Monad - import Char as Data.Char - group: name: default enabled: true imports: - package base rules: # I/O - warn: {lhs: putStrLn (show x), rhs: print x} - warn: {lhs: mapM_ putChar, rhs: putStr} - warn: {lhs: hGetChar stdin, rhs: getChar} - warn: {lhs: hGetLine stdin, rhs: getLine} - warn: {lhs: hGetContents stdin, rhs: getContents} - warn: {lhs: hPutChar stdout, rhs: putChar} - warn: {lhs: hPutStr stdout, rhs: putStr} - warn: {lhs: hPutStrLn stdout, rhs: putStrLn} - warn: {lhs: hPrint stdout, rhs: print} - warn: {lhs: hWaitForInput a 0, rhs: hReady a} - warn: {lhs: hPutStrLn a (show b), rhs: hPrint a b} - warn: {lhs: hIsEOF stdin, rhs: isEOF} - warn: {lhs: withFile f m (\h -> hPutStr h x), rhs: writeFile file x} - warn: {lhs: withFile f m (\h -> hPutStrLn h x), rhs: writeFile file (x ++ "\n")} # EXIT - warn: {lhs: exitWith ExitSuccess, rhs: exitSuccess} # ORD - warn: {lhs: not (a == b), rhs: a /= b, note: incorrect if either value is NaN} - warn: {lhs: not (a /= b), rhs: a == b, note: incorrect if either value is NaN} - warn: {lhs: not (a > b), rhs: a <= b, note: incorrect if either value is NaN} - warn: {lhs: not (a >= b), rhs: a < b, note: incorrect if either value is NaN} - warn: {lhs: not (a < b), rhs: a >= b, note: incorrect if either value is NaN} - warn: {lhs: not (a <= b), rhs: a > b, note: incorrect if either value is NaN} - warn: {lhs: compare x y /= GT, rhs: x <= y} - warn: {lhs: compare x y == LT, rhs: x < y} - warn: {lhs: compare x y /= LT, rhs: x >= y} - warn: {lhs: compare x y == GT, rhs: x > y} - warn: {lhs: compare x y == EQ, rhs: x == y} - warn: {lhs: compare x y /= EQ, rhs: x /= y} - warn: {lhs: head (sort x), rhs: minimum x} - warn: {lhs: last (sort x), rhs: maximum x} - warn: {lhs: head (sortBy f x), rhs: minimumBy f x, side: isCompare f} - warn: {lhs: last (sortBy f x), rhs: maximumBy f x, side: isCompare f} - warn: {lhs: reverse (sort x), rhs: sortBy (flip compare) x, name: Avoid reverse} - warn: {lhs: reverse (sortBy f x), rhs: sortBy (flip f) x, name: Avoid reverse, side: isCompare f} - hint: {lhs: flip (g `on` h), rhs: flip g `on` h, name: Move flip} - hint: {lhs: (f `on` g) `on` h, rhs: f `on` (g . h)} # READ/SHOW - warn: {lhs: showsPrec 0 x "", rhs: show x} - warn: {lhs: readsPrec 0, rhs: reads} - warn: {lhs: showsPrec 0, rhs: shows} - hint: {lhs: showIntAtBase 16 intToDigit, rhs: showHex} - hint: {lhs: showIntAtBase 8 intToDigit, rhs: showOct} # LIST - warn: {lhs: concat (map f x), rhs: concatMap f x} - warn: {lhs: concat (fmap f x), rhs: concatMap f x} - hint: {lhs: "concat [a, b]", rhs: a ++ b} - hint: {lhs: map f (map g x), rhs: map (f . g) x, name: Use map once} - hint: {lhs: concatMap f (map g x), rhs: concatMap (f . g) x, name: Fuse concatMap/map} - hint: {lhs: x !! 0, rhs: head x} - warn: {lhs: take n (repeat x), rhs: replicate n x} - warn: {lhs: map f (replicate n x), rhs: replicate n (f x)} - warn: {lhs: map f (repeat x), rhs: repeat (f x)} - warn: {lhs: "cycle [x]", rhs: repeat x} - warn: {lhs: head (reverse x), rhs: last x} - warn: {lhs: head (drop n x), rhs: x !! n, side: isNat n} - warn: {lhs: reverse (tail (reverse x)), rhs: init x, note: IncreasesLaziness} - warn: {lhs: reverse (reverse x), rhs: x, note: IncreasesLaziness, name: Avoid reverse} - warn: {lhs: isPrefixOf (reverse x) (reverse y), rhs: isSuffixOf x y} - warn: {lhs: "foldr (++) []", rhs: concat} - warn: {lhs: foldr (++) "", rhs: concat} - warn: {lhs: "foldl (++) []", rhs: concat, note: IncreasesLaziness} - warn: {lhs: foldl (++) "", rhs: concat, note: IncreasesLaziness} - warn: {lhs: foldl f (head x) (tail x), rhs: foldl1 f x} - warn: {lhs: foldr f (last x) (init x), rhs: foldr1 f x} - warn: {lhs: "foldr (\\c a -> x : a) []", rhs: "map (\\c -> x)"} - warn: {lhs: span (not . p), rhs: break p} - warn: {lhs: break (not . p), rhs: span p} - warn: {lhs: "(takeWhile p x, dropWhile p x)", rhs: span p x} - warn: {lhs: fst (span p x), rhs: takeWhile p x} - warn: {lhs: snd (span p x), rhs: dropWhile p x} - warn: {lhs: fst (break p x), rhs: takeWhile (not . p) x} - warn: {lhs: snd (break p x), rhs: dropWhile (not . p) x} - warn: {lhs: concatMap (++ "\n"), rhs: unlines} - warn: {lhs: map id, rhs: id} - warn: {lhs: concatMap id, rhs: concat} - warn: {lhs: or (map p x), rhs: any p x} - warn: {lhs: and (map p x), rhs: all p x} - warn: {lhs: "zipWith (,)", rhs: zip} - warn: {lhs: "zipWith3 (,,)", rhs: zip3} - hint: {lhs: length x == 0, rhs: null x, note: IncreasesLaziness} - hint: {lhs: "x == []", rhs: null x} - hint: {lhs: length x /= 0, rhs: not (null x), note: IncreasesLaziness, name: Use null} - hint: {lhs: "\\x -> [x]", rhs: "(:[])", name: "Use :"} - warn: {lhs: map (uncurry f) (zip x y), rhs: zipWith f x y} - hint: {lhs: map f (zip x y), rhs: zipWith (curry f) x y, side: isVar f} - warn: {lhs: not (elem x y), rhs: notElem x y} - hint: {lhs: foldr f z (map g x), rhs: foldr (f . g) z x} - warn: {lhs: "x ++ concatMap (' ':) y", rhs: "unwords (x:y)"} - warn: {lhs: intercalate " ", rhs: unwords} - hint: {lhs: concat (intersperse x y), rhs: intercalate x y, side: notEq x " "} - hint: {lhs: concat (intersperse " " x), rhs: unwords x} - warn: {lhs: null (filter f x), rhs: not (any f x), name: Use any} - warn: {lhs: "filter f x == []", rhs: not (any f x), name: Use any} - warn: {lhs: "filter f x /= []", rhs: any f x} - warn: {lhs: any id, rhs: or} - warn: {lhs: all id, rhs: and} - warn: {lhs: any ((==) a), rhs: elem a, note: ValidInstance Eq a} - warn: {lhs: any (== a), rhs: elem a} - warn: {lhs: any (a ==), rhs: elem a, note: ValidInstance Eq a} - warn: {lhs: all ((/=) a), rhs: notElem a, note: ValidInstance Eq a} - warn: {lhs: all (/= a), rhs: notElem a, note: ValidInstance Eq a} - warn: {lhs: all (a /=), rhs: notElem a, note: ValidInstance Eq a} - warn: {lhs: elem True, rhs: or} - warn: {lhs: notElem False, rhs: and} - warn: {lhs: findIndex ((==) a), rhs: elemIndex a} - warn: {lhs: findIndex (a ==), rhs: elemIndex a} - warn: {lhs: findIndex (== a), rhs: elemIndex a} - warn: {lhs: findIndices ((==) a), rhs: elemIndices a} - warn: {lhs: findIndices (a ==), rhs: elemIndices a} - warn: {lhs: findIndices (== a), rhs: elemIndices a} - warn: {lhs: "lookup b (zip l [0..])", rhs: elemIndex b l} - hint: {lhs: "elem x [y]", rhs: x == y, note: ValidInstance Eq a} - hint: {lhs: "notElem x [y]", rhs: x /= y, note: ValidInstance Eq a} - hint: {lhs: length x >= 0, rhs: "True", name: Length always non-negative} - hint: {lhs: length x > 0, rhs: not (null x), note: IncreasesLaziness, name: Use null} - hint: {lhs: length x >= 1, rhs: not (null x), note: IncreasesLaziness, name: Use null} - warn: {lhs: take i x, rhs: "[]", side: isNegZero i, name: Take on a non-positive} - warn: {lhs: drop i x, rhs: x, side: isNegZero i, name: Drop on a non-positive} - warn: {lhs: last (scanl f z x), rhs: foldl f z x} - warn: {lhs: head (scanr f z x), rhs: foldr f z x} - warn: {lhs: iterate id, rhs: repeat} - warn: {lhs: zipWith f (repeat x), rhs: map (f x)} - warn: {lhs: zipWith f y (repeat z), rhs: map (\x -> f x z) y} # TRAVERSABLES - warn: {lhs: sequenceA (map f x), rhs: traverse f x} - warn: {lhs: sequenceA (fmap f x), rhs: traverse f x} - warn: {lhs: sequence (fmap f x), rhs: traverse f x} - warn: {lhs: sequenceA_ (map f x), rhs: traverse_ f x} - warn: {lhs: sequenceA_ (fmap f x), rhs: traverse_ f x} # BY - warn: {lhs: deleteBy (==), rhs: delete} - warn: {lhs: groupBy (==), rhs: group} - warn: {lhs: insertBy compare, rhs: insert} - warn: {lhs: intersectBy (==), rhs: intersect} - warn: {lhs: maximumBy compare, rhs: maximum} - warn: {lhs: minimumBy compare, rhs: minimum} - warn: {lhs: nubBy (==), rhs: nub} - warn: {lhs: sortBy compare, rhs: sort} - warn: {lhs: unionBy (==), rhs: union} # FOLDS - warn: {lhs: foldr (>>) (return ()), rhs: sequence_} - warn: {lhs: foldr (&&) True, rhs: and} - warn: {lhs: foldl (&&) True, rhs: and, note: IncreasesLaziness} - warn: {lhs: foldr1 (&&) , rhs: and, note: "RemovesError on `[]`"} - warn: {lhs: foldl1 (&&) , rhs: and, note: "RemovesError on `[]`"} - warn: {lhs: foldr (||) False, rhs: or} - warn: {lhs: foldl (||) False, rhs: or, note: IncreasesLaziness} - warn: {lhs: foldr1 (||) , rhs: or, note: "RemovesError on `[]`"} - warn: {lhs: foldl1 (||) , rhs: or, note: "RemovesError on `[]`"} - warn: {lhs: foldl (+) 0, rhs: sum} - warn: {lhs: foldr (+) 0, rhs: sum} - warn: {lhs: foldl1 (+) , rhs: sum, note: "RemovesError on `[]`"} - warn: {lhs: foldr1 (+) , rhs: sum, note: "RemovesError on `[]`"} - warn: {lhs: foldl (*) 1, rhs: product} - warn: {lhs: foldr (*) 1, rhs: product} - warn: {lhs: foldl1 (*) , rhs: product, note: "RemovesError on `[]`"} - warn: {lhs: foldr1 (*) , rhs: product, note: "RemovesError on `[]`"} - warn: {lhs: foldl1 max , rhs: maximum} - warn: {lhs: foldr1 max , rhs: maximum} - warn: {lhs: foldl1 min , rhs: minimum} - warn: {lhs: foldr1 min , rhs: minimum} - warn: {lhs: foldr mplus mzero, rhs: msum} # FUNCTION - warn: {lhs: \x -> x, rhs: id} - warn: {lhs: \x y -> x, rhs: const} - warn: {lhs: "\\(x,y) -> y", rhs: snd} - warn: {lhs: "\\(x,y) -> x", rhs: fst} - hint: {lhs: "\\x y -> f (x,y)", rhs: curry f, name: Use curry} - hint: {lhs: "\\(x,y) -> f x y", rhs: uncurry f, note: IncreasesLaziness, name: Use uncurry} - warn: {lhs: ($) . f, rhs: f, name: Redundant $} - warn: {lhs: (f $), rhs: f, name: Redundant $} - hint: {lhs: \x -> y, rhs: const y, side: isAtom y && not (isWildcard y)} # isWildcard because some people like to put brackets round them even though they are atomic - warn: {lhs: flip f x y, rhs: f y x, side: isApp original, name: Redundant flip} - warn: {lhs: id x, rhs: x, side: not (isTypeApp x), name: Evaluate} - warn: {lhs: id . x, rhs: x, name: Redundant id} - warn: {lhs: x . id, rhs: x, name: Redundant id} # CHAR - warn: {lhs: a >= 'a' && a <= 'z', rhs: isAsciiLower a} - warn: {lhs: a >= 'A' && a <= 'Z', rhs: isAsciiUpper a} - warn: {lhs: a >= '0' && a <= '9', rhs: isDigit a} - warn: {lhs: a >= '0' && a <= '7', rhs: isOctDigit a} - warn: {lhs: isLower a || isUpper a, rhs: isAlpha a} - warn: {lhs: isUpper a || isLower a, rhs: isAlpha a} # BOOL - warn: {lhs: x == True, rhs: x, name: Redundant ==} - hint: {lhs: x == False, rhs: not x, name: Redundant ==} - warn: {lhs: True == a, rhs: a, name: Redundant ==} - hint: {lhs: False == a, rhs: not a, name: Redundant ==} - warn: {lhs: a /= True, rhs: not a, name: Redundant /=} - hint: {lhs: a /= False, rhs: a, name: Redundant /=} - warn: {lhs: True /= a, rhs: not a, name: Redundant /=} - hint: {lhs: False /= a, rhs: a, name: Redundant /=} - warn: {lhs: if a then x else x, rhs: x, note: IncreasesLaziness, name: Redundant if} - warn: {lhs: if a then True else False, rhs: a, name: Redundant if} - warn: {lhs: if a then False else True, rhs: not a, name: Redundant if} - warn: {lhs: if a then t else (if b then t else f), rhs: if a || b then t else f, name: Redundant if} - warn: {lhs: if a then (if b then t else f) else f, rhs: if a && b then t else f, name: Redundant if} - warn: {lhs: if x then True else y, rhs: x || y, side: notEq y False, name: Redundant if} - warn: {lhs: if x then y else False, rhs: x && y, side: notEq y True, name: Redundant if} - hint: {lhs: "case a of {True -> t; False -> f}", rhs: if a then t else f, name: Use if} - hint: {lhs: "case a of {False -> f; True -> t}", rhs: if a then t else f, name: Use if} - hint: {lhs: "case a of {True -> t; _ -> f}", rhs: if a then t else f, name: Use if} - hint: {lhs: "case a of {False -> f; _ -> t}", rhs: if a then t else f, name: Use if} - hint: {lhs: "if c then (True, x) else (False, x)", rhs: "(c, x)", note: IncreasesLaziness, name: Redundant if} - hint: {lhs: "if c then (False, x) else (True, x)", rhs: "(not c, x)", note: IncreasesLaziness, name: Redundant if} - hint: {lhs: "or [x, y]", rhs: x || y} - hint: {lhs: "or [x, y, z]", rhs: x || y || z} - hint: {lhs: "and [x, y]", rhs: x && y} - hint: {lhs: "and [x, y, z]", rhs: x && y && z} - warn: {lhs: if x then False else y, rhs: not x && y, side: notEq y True, name: Redundant if} - warn: {lhs: if x then y else True, rhs: not x || y, side: notEq y False, name: Redundant if} - warn: {lhs: not (not x), rhs: x, name: Redundant not} # warn "Too strict if": {lhs: if c then f x else f y, rhs: f (if c then x else y), note: IncreasesLaziness} # also breaks types, see #87 # ARROW - warn: {lhs: id *** g, rhs: second g} - warn: {lhs: f *** id, rhs: first f} - warn: {lhs: zip (map f x) (map g x), rhs: map (f Control.Arrow.&&& g) x} - hint: {lhs: "\\(x,y) -> (f x, g y)", rhs: f Control.Arrow.*** g} - hint: {lhs: "\\x -> (f x, g x)", rhs: f Control.Arrow.&&& g} - hint: {lhs: "\\(x,y) -> (f x,y)", rhs: Control.Arrow.first f} - hint: {lhs: "\\(x,y) -> (x,f y)", rhs: Control.Arrow.second f} - hint: {lhs: "(f (fst x), g (snd x))", rhs: (f Control.Arrow.*** g) x} - hint: {lhs: "(fst x, snd x)", rhs: x, note: DecreasesLaziness, name: Redundant pair} # FUNCTOR - warn: {lhs: fmap f (fmap g x), rhs: fmap (f . g) x, name: Functor law} - warn: {lhs: f <$> g <$> x, rhs: f . g <$> x, name: Functor law} - warn: {lhs: fmap id, rhs: id, name: Functor law} - warn: {lhs: id <$> x, rhs: x, name: Functor law} - hint: {lhs: fmap f $ x, rhs: f Control.Applicative.<$> x, side: isApp x || isAtom x} - hint: {lhs: \x -> a <$> b x, rhs: fmap a . b} - hint: {lhs: x *> pure y, rhs: x Data.Functor.$> y} - hint: {lhs: x *> return y, rhs: x Data.Functor.$> y} - hint: {lhs: pure x <* y, rhs: x Data.Functor.<$ y} - hint: {lhs: return x <* y, rhs: x Data.Functor.<$ y} # MONAD - warn: {lhs: return a >>= f, rhs: f a, name: "Monad law, left identity"} - warn: {lhs: f =<< return a, rhs: f a, name: "Monad law, left identity"} - warn: {lhs: m >>= return, rhs: m, name: "Monad law, right identity"} - warn: {lhs: return =<< m, rhs: m, name: "Monad law, right identity"} - warn: {lhs: liftM, rhs: fmap} - warn: {lhs: liftA, rhs: fmap} - hint: {lhs: m >>= return . f, rhs: f <$> m} - hint: {lhs: return . f =<< m, rhs: f <$> m} - warn: {lhs: if x then y else return (), rhs: Control.Monad.when x $ _noParen_ y, side: not (isAtom y)} - warn: {lhs: if x then y else return (), rhs: Control.Monad.when x y, side: isAtom y} - warn: {lhs: if x then return () else y, rhs: Control.Monad.unless x $ _noParen_ y, side: isAtom y} - warn: {lhs: if x then return () else y, rhs: Control.Monad.unless x y, side: isAtom y} - warn: {lhs: sequence (map f x), rhs: mapM f x} - warn: {lhs: sequence_ (map f x), rhs: mapM_ f x} - hint: {lhs: flip mapM, rhs: Control.Monad.forM} - hint: {lhs: flip mapM_, rhs: Control.Monad.forM_} - hint: {lhs: flip forM, rhs: mapM} - hint: {lhs: flip forM_, rhs: mapM_} - warn: {lhs: when (not x), rhs: unless x} - warn: {lhs: x >>= id, rhs: Control.Monad.join x} - warn: {lhs: id =<< x, rhs: Control.Monad.join x} - hint: {lhs: a >> return (), rhs: Control.Monad.void a, side: isAtom a || isApp a} - warn: {lhs: fmap (const ()), rhs: Control.Monad.void} - warn: {lhs: const () <$> x, rhs: Control.Monad.void x} - warn: {lhs: flip (>=>), rhs: (<=<)} - warn: {lhs: flip (<=<), rhs: (>=>)} - warn: {lhs: flip (>>=), rhs: (=<<)} - warn: {lhs: flip (=<<), rhs: (>>=)} - hint: {lhs: \x -> f x >>= g, rhs: f Control.Monad.>=> g} - hint: {lhs: \x -> f =<< g x, rhs: f Control.Monad.<=< g} - warn: {lhs: a >> forever a, rhs: forever a} - hint: {lhs: liftM2 id, rhs: ap} - warn: {lhs: mapM (uncurry f) (zip l m), rhs: zipWithM f l m} - warn: {lhs: mapM_ (void . f), rhs: mapM_ f} - warn: {lhs: mapM_ (void f), rhs: mapM_ f} - warn: {lhs: forM_ x (void . f), rhs: forM_ x f} - warn: {lhs: forM_ x (void f), rhs: forM_ x f} - warn: {lhs: void (mapM f x), rhs: mapM_ f x} - warn: {lhs: void (forM x f), rhs: forM_ x f} # STATE MONAD - warn: {lhs: fst (runState x y), rhs: evalState x y} - warn: {lhs: snd (runState x y), rhs: execState x y} # MONAD LIST - warn: {lhs: fmap unzip (mapM f x), rhs: Control.Monad.mapAndUnzipM f x} - warn: {lhs: sequence (zipWith f x y), rhs: Control.Monad.zipWithM f x y} - warn: {lhs: sequence_ (zipWith f x y), rhs: Control.Monad.zipWithM_ f x y} - warn: {lhs: sequence (replicate n x), rhs: Control.Monad.replicateM n x} - warn: {lhs: sequence_ (replicate n x), rhs: Control.Monad.replicateM_ n x} - warn: {lhs: mapM f (replicate n x), rhs: Control.Monad.replicateM n (f x)} - warn: {lhs: mapM_ f (replicate n x), rhs: Control.Monad.replicateM_ n (f x)} - warn: {lhs: mapM f (map g x), rhs: mapM (f . g) x} - warn: {lhs: mapM_ f (map g x), rhs: mapM_ (f . g) x} - warn: {lhs: mapM id, rhs: sequence} - warn: {lhs: mapM_ id, rhs: sequence_} # APPLICATIVE / TRAVERSABLE - warn: {lhs: flip traverse, rhs: for} - warn: {lhs: flip for, rhs: traverse} - warn: {lhs: flip traverse_, rhs: for_} - warn: {lhs: flip for_, rhs: traverse_} - warn: {lhs: foldr (*>) (pure ()), rhs: sequenceA_} - warn: {lhs: foldr (<|>) empty, rhs: asum} - warn: {lhs: liftA2 (flip ($)), rhs: (<**>)} - warn: {lhs: Just <$> a <|> pure Nothing, rhs: optional a} - hint: {lhs: m >>= pure . f, rhs: f <$> m} - hint: {lhs: pure . f =<< m, rhs: f <$> m} # LIST COMP - hint: {lhs: "if b then [x] else []", rhs: "[x | b]", name: Use list comprehension} - hint: {lhs: "[x | x <- y]", rhs: "y", side: isVar x, name: Redundant list comprehension} # SEQ - warn: {lhs: x `seq` x, rhs: x, name: Redundant seq} - warn: {lhs: join seq, rhs: id, name: Redundant seq} - warn: {lhs: id $! x, rhs: x, name: Redundant $!} - warn: {lhs: x `seq` y, rhs: "y", side: isWHNF x, name: Redundant seq} - warn: {lhs: f $! x, rhs: f x, side: isWHNF x, name: Redundant $!} - warn: {lhs: evaluate x, rhs: return x, side: isWHNF x, name: Redundant evaluate} # TUPLE - warn: {lhs: fst (unzip x), rhs: map fst x} - warn: {lhs: snd (unzip x), rhs: map snd x} # MAYBE - warn: {lhs: maybe x id, rhs: Data.Maybe.fromMaybe x} - warn: {lhs: maybe False (const True), rhs: Data.Maybe.isJust} - warn: {lhs: maybe True (const False), rhs: Data.Maybe.isNothing} - warn: {lhs: maybe False (== x), rhs: (== Just x)} - warn: {lhs: maybe True (/= x), rhs: (/= Just x)} - warn: {lhs: not (isNothing x), rhs: isJust x} - warn: {lhs: not (isJust x), rhs: isNothing x} - warn: {lhs: "maybe [] (:[])", rhs: maybeToList} - warn: {lhs: catMaybes (map f x), rhs: mapMaybe f x} - hint: {lhs: case x of Nothing -> y; Just a -> a , rhs: fromMaybe y x} - warn: {lhs: if isNothing x then y else f (fromJust x), rhs: maybe y f x} - warn: {lhs: if isJust x then f (fromJust x) else y, rhs: maybe y f x} - warn: {lhs: maybe Nothing (Just . f), rhs: fmap f} - hint: {lhs: map fromJust . filter isJust , rhs: Data.Maybe.catMaybes} - warn: {lhs: x == Nothing , rhs: isNothing x} - warn: {lhs: Nothing == x , rhs: isNothing x} - warn: {lhs: x /= Nothing , rhs: Data.Maybe.isJust x} - warn: {lhs: Nothing /= x , rhs: Data.Maybe.isJust x} - warn: {lhs: concatMap (maybeToList . f), rhs: Data.Maybe.mapMaybe f} - warn: {lhs: concatMap maybeToList, rhs: catMaybes} - warn: {lhs: maybe n Just x, rhs: x Control.Applicative.<|> n} - hint: {lhs: case x of Just a -> a; Nothing -> y, rhs: fromMaybe y x} - warn: {lhs: if isNothing x then y else fromJust x, rhs: fromMaybe y x} - warn: {lhs: if isJust x then fromJust x else y, rhs: fromMaybe y x} - warn: {lhs: isJust x && (fromJust x == y), rhs: x == Just y} - warn: {lhs: mapMaybe f (map g x), rhs: mapMaybe (f . g) x} - warn: {lhs: fromMaybe a (fmap f x), rhs: maybe a f x} - warn: {lhs: fromMaybe a (f <$> x), rhs: maybe a f x} - warn: {lhs: mapMaybe id, rhs: catMaybes} - hint: {lhs: "[x | Just x <- a]", rhs: Data.Maybe.catMaybes a} - hint: {lhs: case m of Nothing -> Nothing; Just x -> x, rhs: Control.Monad.join m} - hint: {lhs: maybe Nothing id, rhs: join} - hint: {lhs: maybe (f x) (f . g), rhs: f . maybe x g, note: IncreasesLaziness, name: Too strict maybe} # EITHER - warn: {lhs: "[a | Left a <- a]", rhs: lefts a} - warn: {lhs: "[a | Right a <- a]", rhs: rights a} - warn: {lhs: either Left (Right . f), rhs: fmap f} # INFIX - hint: {lhs: elem x y, rhs: x `elem` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: notElem x y, rhs: x `notElem` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: isInfixOf x y, rhs: x `isInfixOf` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: isSuffixOf x y, rhs: x `isSuffixOf` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: isPrefixOf x y, rhs: x `isPrefixOf` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: union x y, rhs: x `union` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: intersect x y, rhs: x `intersect` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} # MATHS - warn: {lhs: fromIntegral x, rhs: x, side: isLitInt x, name: Redundant fromIntegral} - warn: {lhs: fromInteger x, rhs: x, side: isLitInt x, name: Redundant fromInteger} - hint: {lhs: x + negate y, rhs: x - y} - hint: {lhs: 0 - x, rhs: negate x} - warn: {lhs: negate (negate x), rhs: x, name: Redundant negate} - hint: {lhs: log y / log x, rhs: logBase x y} - hint: {lhs: sin x / cos x, rhs: tan x} - hint: {lhs: n `rem` 2 == 0, rhs: even n} - hint: {lhs: n `rem` 2 /= 0, rhs: odd n} - hint: {lhs: not (even x), rhs: odd x} - hint: {lhs: not (odd x), rhs: even x} - hint: {lhs: x ** 0.5, rhs: sqrt x} - hint: {lhs: x ^ 0, rhs: "1", name: Use 1} - hint: {lhs: round (x - 0.5), rhs: floor x} # CONCURRENT - hint: {lhs: mapM_ (writeChan a), rhs: writeList2Chan a} # EXCEPTION - hint: {lhs: flip Control.Exception.catch, rhs: handle} - hint: {lhs: flip handle, rhs: Control.Exception.catch} - hint: {lhs: flip (catchJust p), rhs: handleJust p} - hint: {lhs: flip (handleJust p), rhs: catchJust p} - hint: {lhs: Control.Exception.bracket b (const a) (const t), rhs: Control.Exception.bracket_ b a t} - hint: {lhs: Control.Exception.bracket (openFile x y) hClose, rhs: withFile x y} - hint: {lhs: Control.Exception.bracket (openBinaryFile x y) hClose, rhs: withBinaryFile x y} - hint: {lhs: throw (ErrorCall a), rhs: error a} - warn: {lhs: toException NonTermination, rhs: nonTermination} - warn: {lhs: toException NestedAtomically, rhs: nestedAtomically} # STOREABLE/PTR - hint: {lhs: castPtr nullPtr, rhs: nullPtr} - hint: {lhs: castPtr (castPtr x), rhs: castPtr x} - hint: {lhs: plusPtr (castPtr x), rhs: plusPtr x} - hint: {lhs: minusPtr (castPtr x), rhs: minusPtr x} - hint: {lhs: minusPtr x (castPtr y), rhs: minusPtr x y} - hint: {lhs: peekByteOff (castPtr x), rhs: peekByteOff x} - hint: {lhs: pokeByteOff (castPtr x), rhs: pokeByteOff x} # WEAK POINTERS - warn: {lhs: mkWeak a a b, rhs: mkWeakPtr a b} - warn: {lhs: "mkWeak a (a, b) c", rhs: mkWeakPair a b c} # FOLDABLE - warn: {lhs: case m of Nothing -> return (); Just x -> f x, rhs: Data.Foldable.forM_ m f} - warn: {lhs: when (isJust m) (f (fromJust m)), rhs: Data.Foldable.forM_ m f} # EVALUATE - warn: {lhs: True && x, rhs: x, name: Evaluate} - warn: {lhs: False && x, rhs: "False", name: Evaluate} - warn: {lhs: True || x, rhs: "True", name: Evaluate} - warn: {lhs: False || x, rhs: x, name: Evaluate} - warn: {lhs: not True, rhs: "False", name: Evaluate} - warn: {lhs: not False, rhs: "True", name: Evaluate} - warn: {lhs: Nothing >>= k, rhs: Nothing, name: Evaluate} - warn: {lhs: k =<< Nothing, rhs: Nothing, name: Evaluate} - warn: {lhs: either f g (Left x), rhs: f x, name: Evaluate} - warn: {lhs: either f g (Right y), rhs: g y, name: Evaluate} - warn: {lhs: "fst (x,y)", rhs: x, name: Evaluate} - warn: {lhs: "snd (x,y)", rhs: "y", name: Evaluate} - warn: {lhs: f (fst p) (snd p), rhs: uncurry f p, name: Evaluate} - warn: {lhs: "init [x]", rhs: "[]", name: Evaluate} - warn: {lhs: "null []", rhs: "True", name: Evaluate} - warn: {lhs: "length []", rhs: "0", name: Evaluate} - warn: {lhs: "foldl f z []", rhs: z, name: Evaluate} - warn: {lhs: "foldr f z []", rhs: z, name: Evaluate} - warn: {lhs: "foldr1 f [x]", rhs: x, name: Evaluate} - warn: {lhs: "scanr f z []", rhs: "[z]", name: Evaluate} - warn: {lhs: "scanr1 f []", rhs: "[]", name: Evaluate} - warn: {lhs: "scanr1 f [x]", rhs: "[x]", name: Evaluate} - warn: {lhs: "take n []", rhs: "[]", note: IncreasesLaziness, name: Evaluate} - warn: {lhs: "drop n []", rhs: "[]", note: IncreasesLaziness, name: Evaluate} - warn: {lhs: "takeWhile p []", rhs: "[]", name: Evaluate} - warn: {lhs: "dropWhile p []", rhs: "[]", name: Evaluate} - warn: {lhs: "span p []", rhs: "([],[])", name: Evaluate} - warn: {lhs: lines "", rhs: "[]", name: Evaluate} - warn: {lhs: "unwords []", rhs: "\"\"", name: Evaluate} - warn: {lhs: x - 0, rhs: x, name: Evaluate} - warn: {lhs: x * 1, rhs: x, name: Evaluate} - warn: {lhs: x / 1, rhs: x, name: Evaluate} - warn: {lhs: "concat [a]", rhs: a, name: Evaluate} - warn: {lhs: "concat []", rhs: "[]", name: Evaluate} - warn: {lhs: "zip [] []", rhs: "[]", name: Evaluate} - warn: {lhs: const x y, rhs: x, name: Evaluate} # FOLDABLE + TUPLES - warn: {lhs: "foldr f z (x,b)", rhs: f b z, name: Using foldr on tuple} - warn: {lhs: "foldr' f z (x,b)", rhs: f b z, name: Using foldr' on tuple} - warn: {lhs: "foldl f z (x,b)", rhs: f z b, name: Using foldl on tuple} - warn: {lhs: "foldl' f z (x,b)", rhs: f z b, name: Using foldl' on tuple} - warn: {lhs: "foldMap f (x,b)", rhs: f b, name: Using foldMap on tuple} - warn: {lhs: "foldr1 f (x,b)", rhs: b, name: Using foldr1 on tuple} - warn: {lhs: "foldl1 f (x,b)", rhs: b, name: Using foldl1 on tuple} - warn: {lhs: "elem e (x,b)", rhs: e == b, name: Using elem on tuple} - warn: {lhs: "fold (x,b)", rhs: b, name: Using fold on tuple} - warn: {lhs: "toList (x,b)", rhs: b, name: Using toList on tuple} - warn: {lhs: "maximum (x,b)", rhs: b, name: Using maximum on tuple} - warn: {lhs: "minimum (x,b)", rhs: b, name: Using minimum on tuple} - warn: {lhs: "sum (x,b)", rhs: b, name: Using sum on tuple} - warn: {lhs: "product (x,b)", rhs: b, name: Using product on tuple} - warn: {lhs: "concat (x,b)", rhs: b, name: Using concat on tuple} - warn: {lhs: "and (x,b)", rhs: b, name: Using and on tuple} - warn: {lhs: "or (x,b)", rhs: b, name: Using or on tuple} - warn: {lhs: "any f (x,b)", rhs: f b, name: Using any on tuple} - warn: {lhs: "all f (x,b)", rhs: f b, name: Using all on tuple} - warn: {lhs: "foldr f z (x,y,b)", rhs: f b z, name: Using foldr on tuple} - warn: {lhs: "foldr' f z (x,y,b)", rhs: f b z, name: Using foldr' on tuple} - warn: {lhs: "foldl f z (x,y,b)", rhs: f z b, name: Using foldl on tuple} - warn: {lhs: "foldl' f z (x,y,b)", rhs: f z b, name: Using foldl' on tuple} - warn: {lhs: "foldMap f (x,y,b)", rhs: f b, name: Using foldMap on tuple} - warn: {lhs: "foldr1 f (x,y,b)", rhs: b, name: Using foldr1 on tuple} - warn: {lhs: "foldl1 f (x,y,b)", rhs: b, name: Using foldl1 on tuple} - warn: {lhs: "elem e (x,y,b)", rhs: e == b, name: Using elem on tuple} - warn: {lhs: "fold (x,y,b)", rhs: b, name: Using fold on tuple} - warn: {lhs: "toList (x,y,b)", rhs: b, name: Using toList on tuple} - warn: {lhs: "maximum (x,y,b)", rhs: b, name: Using maximum on tuple} - warn: {lhs: "minimum (x,y,b)", rhs: b, name: Using minimum on tuple} - warn: {lhs: "sum (x,y,b)", rhs: b, name: Using sum on tuple} - warn: {lhs: "product (x,y,b)", rhs: b, name: Using product on tuple} - warn: {lhs: "concat (x,y,b)", rhs: b, name: Using concat on tuple} - warn: {lhs: "and (x,y,b)", rhs: b, name: Using and on tuple} - warn: {lhs: "or (x,y,b)", rhs: b, name: Using or on tuple} - warn: {lhs: "any f (x,y,b)", rhs: f b, name: Using any on tuple} - warn: {lhs: "all f (x,y,b)", rhs: f b, name: Using all on tuple} - warn: {lhs: null x , rhs: "False", side: isTuple x, name: Using null on tuple} - warn: {lhs: length x, rhs: "1" , side: isTuple x, name: Using length on tuple} - group: name: generalise enabled: false imports: - package base rules: - warn: {lhs: map, rhs: fmap} - warn: {lhs: a ++ b, rhs: a <> b} - group: name: dollar enabled: false imports: - package base rules: - warn: {lhs: a $ b $ c, rhs: a . b $ c} # # yes = concat . map f -- concatMap f # yes = foo . bar . concat . map f . baz . bar -- concatMap f . baz . bar # yes = map f (map g x) -- map (f . g) x # yes = concat.map (\x->if x==e then l' else [x]) -- concatMap (\x->if x==e then l' else [x]) # yes = f x where f x = concat . map head -- concatMap head # yes = concat . map f . g -- concatMap f . g # yes = concat $ map f x -- concatMap f x # yes = "test" ++ concatMap (' ':) ["of","this"] -- unwords ("test":["of","this"]) # yes = if f a then True else b -- f a || b # yes = not (a == b) -- a /= b # yes = not (a /= b) -- a == b # yes = not . (a ==) -- (a /=) # yes = not . (== a) -- (/= a) # yes = not . (a /=) -- (a ==) # yes = not . (/= a) -- (== a) # yes = if a then 1 else if b then 1 else 2 -- if a || b then 1 else 2 # no = if a then 1 else if b then 3 else 2 # yes = a >>= return . bob -- bob <$> a # yes = return . bob =<< a -- bob <$> a # yes = m alice >>= pure . b -- b <$> m alice # yes = pure .b =<< m alice -- b <$> m alice # yes = asciiCI "hi" *> pure Hi -- asciiCI "hi" Data.Functor.$> Hi # yes = asciiCI "bye" *> return Bye -- asciiCI "bye" Data.Functor.$> Bye # yes = pure x <* y -- x Data.Functor.<$ y # yes = return x <* y -- x Data.Functor.<$ y # yes = (x !! 0) + (x !! 2) -- head x # yes = if b < 42 then [a] else [] -- [a | b < 42] # no = take n (foo xs) == "hello" # yes = head (reverse xs) -- last xs # yes = reverse xs `isPrefixOf` reverse ys -- isSuffixOf xs ys # no = putStrLn $ show (length xs) ++ "Test" # yes = ftable ++ map (\ (c, x) -> (toUpper c, urlEncode x)) ftable -- toUpper Control.Arrow.*** urlEncode # yes = map (\(a,b) -> a) xs -- fst # yes = map (\(a,_) -> a) xs -- fst # yes = readFile $ args !! 0 -- head args # yes = if Debug `elem` opts then ["--debug"] else [] -- ["--debug" | Debug `elem` opts] # yes = if nullPS s then return False else if headPS s /= '\n' then return False else alter_input tailPS >> return True \ # -- if nullPS s || (headPS s /= '\n') then return False else alter_input tailPS >> return True # yes = if foo then do stuff; moreStuff; lastOfTheStuff else return () \ # -- Control.Monad.when foo $ do stuff ; moreStuff ; lastOfTheStuff # yes = if foo then stuff else return () -- Control.Monad.when foo stuff # yes = foo $ \(a, b) -> (a, y + b) -- Control.Arrow.second ((+) y) # no = foo $ \(a, b) -> (a, a + b) # yes = map (uncurry (+)) $ zip [1 .. 5] [6 .. 10] -- zipWith (+) [1 .. 5] [6 .. 10] # no = do iter <- textBufferGetTextIter tb ; textBufferSelectRange tb iter iter # no = flip f x $ \y -> y*y+y # no = \x -> f x (g x) # no = foo (\ v -> f v . g) # yes = concat . intersperse " " -- unwords # yes = Prelude.concat $ intersperse " " xs -- unwords xs # yes = concat $ Data.List.intersperse " " xs -- unwords xs # yes = if a then True else False -- a # yes = if x then true else False -- x && true # yes = elem x y -- x `elem` y # yes = foo (elem x y) -- x `elem` y # no = x `elem` y # no = elem 1 [] : [] # test a = foo (\x -> True) -- const True # test a = foo (\_ -> True) -- const True # test a = foo (\x -> x) -- id # h a = flip f x (y z) -- f (y z) x # h a = flip f x $ y z # yes x = case x of {True -> a ; False -> b} -- if x then a else b # yes x = case x of {False -> a ; _ -> b} -- if x then b else a # no = const . ok . toResponse $ "saved" # yes = case x z of Nothing -> y z; Just pat -> pat -- fromMaybe (y z) (x z) # yes = if p then s else return () -- Control.Monad.when p s # warn = a $$$$ b $$$$ c ==> a . b $$$$$ c # yes = when (not . null $ asdf) -- unless (null asdf) # yes = id 1 -- 1 # yes = case concat (map f x) of [] -> [] -- concatMap f x # yes = [v | v <- xs] -- xs # no = [Left x | Left x <- xs] # when p s = if p then s else return () # no = x ^^ 18.5 # instance Arrow (->) where first f = f *** id # yes = fromInteger 12 -- 12 # import Prelude hiding (catch); no = catch # import Control.Exception as E; no = E.catch # main = do f; putStrLn $ show x -- print x # main = map (writer,) $ map arcObj $ filter (rdfPredEq (Res dctreferences)) ts -- map ((writer,) . arcObj) (filter (rdfPredEq (Res dctreferences)) ts) # h x y = return $! (x, y) -- return (x, y) # h x y = return $! x # getInt = do { x <- readIO "0"; return $! (x :: Int) } # foo = evaluate [12] -- return [12] # test = \ a -> f a >>= \ b -> return (a, b) # fooer input = catMaybes . map Just $ input -- mapMaybe Just # yes = mapMaybe id -- catMaybes # main = print $ map (\_->5) [2,3,5] -- const 5 # main = head $ drop n x # main = head $ drop (-3) x -- x # main = head $ drop 2 x -- x !! 2 # main = drop 0 x -- x # main = take 0 x -- [] # main = take (-5) x -- [] # main = take (-y) x # main = take 4 x # main = let (first, rest) = (takeWhile p l, dropWhile p l) in rest -- span p l # main = map $ \ d -> ([| $d |], [| $d |]) # pairs (x:xs) = map (\y -> (x,y)) xs ++ pairs xs # {-# ANN foo "HLint: ignore" #-};foo = map f (map g x) -- @Ignore ??? # yes = fmap lines $ abc 123 -- lines Control.Applicative.<$> abc 123 # no = fmap lines $ abc $ def 123 # test = foo . not . not -- id # test = map (not . not) xs -- id # used = not . not . any (`notElem` special) . fst . derives -- any (`notElem` special) . fst . derives # test = foo . id . map -- map # test = food id xs # yes = baz baz >> return () -- Control.Monad.void (baz baz) # no = foo >>= bar >>= something >>= elsee >> return () # no = f (#) x # data Pair = P {a :: !Int}; foo = return $! P{a=undefined} # data Pair = P {a :: !Int}; foo = return $! P undefined # foo = return $! Just undefined -- return (Just undefined) # foo = return $! (a,b) -- return (a,b) # foo = return $! 1 # foo = return $! "test" # bar = [x | (x,_) <- pts] # return' x = x `seq` return x # foo = last (sortBy (compare `on` fst) xs) -- maximumBy (compare `on` fst) xs # g = \ f -> parseFile f >>= (\ cu -> return (f, cu)) # foo = bar $ \(x,y) -> x x y # foo = (\x -> f x >>= g) -- f Control.Monad.>=> g # foo = (\f -> h f >>= g) -- h Control.Monad.>=> g # foo = (\f -> h f >>= f) # foo = bar $ \x -> [x,y] # foo = bar $ \x -> [z,y] -- const [z,y] # f condition tChar tBool = if condition then _monoField tChar else _monoField tBool # foo = maybe Bar{..} id -- Data.Maybe.fromMaybe Bar{..} # foo = (\a -> Foo {..}) 1 # foo = zipWith SymInfo [0 ..] (repeat ty) -- map (\ x -> SymInfo x ty) [0 ..] # f rec = rec # mean x = fst $ foldl (\(m, n) x' -> (m+(x'-m)/(n+1),n+1)) (0,0) x # {-# LANGUAGE TypeApplications #-} \ # foo = id @Int # foo = id 12 -- 12 # yes = foldr (\ curr acc -> (+ 1) curr : acc) [] -- map (\ curr -> (+ 1) curr) # yes = foldr (\ curr acc -> curr + curr : acc) [] -- map (\ curr -> curr + curr) # # import Prelude \ # yes = flip mapM -- Control.Monad.forM # import Control.Monad \ # yes = flip mapM -- forM # import Control.Monad(forM) \ # yes = flip mapM -- forM # import Control.Monad(forM_) \ # yes = flip mapM -- Control.Monad.forM # import qualified Control.Monad \ # yes = flip mapM -- Control.Monad.forM # import qualified Control.Monad as CM \ # yes = flip mapM -- CM.forM # import qualified Control.Monad as CM(forM,filterM) \ # yes = flip mapM -- CM.forM # import Control.Monad as CM(forM,filterM) \ # yes = flip mapM -- forM # import Control.Monad hiding (forM) \ # yes = flip mapM -- Control.Monad.forM # import Control.Monad hiding (filterM) \ # yes = flip mapM -- forM # import qualified Data.Text.Lazy as DTL \ # main = DTL.concat $ map (`DTL.snoc` '-') [DTL.pack "one", DTL.pack "two", DTL.pack "three"] # import Text.Blaze.Html5.Attributes as A \ # main = A.id (stringValue id') # import Prelude((==)) \ # import qualified Prelude as P \ # main = P.length xs == 0 -- P.null xs # hlint-2.0.11/data/hlint.ghci0000644000000000000000000000244413210071537013765 0ustar0000000000000000-- -*- mode: haskell; -*- -- Begin copied material. -- :{ :def redir \varcmd -> return $ case break Data.Char.isSpace varcmd of (var,_:cmd) -> unlines [":set -fno-print-bind-result" ,"tmp <- System.Directory.getTemporaryDirectory" ,"(f,h) <- System.IO.openTempFile tmp \"ghci\"" ,"sto <- GHC.IO.Handle.hDuplicate System.IO.stdout" ,"GHC.IO.Handle.hDuplicateTo h System.IO.stdout" ,"System.IO.hClose h" ,cmd ,"GHC.IO.Handle.hDuplicateTo sto System.IO.stdout" ,"let readFileNow f = readFile f >>= \\t->Data.List.length t `seq` return t" ,var++" <- readFileNow f" ,"System.Directory.removeFile f" ] _ -> "putStrLn \"usage: :redir \"" :} --- Integration with the hlint code style tool :{ :def hlint \extra -> return $ unlines [":unset +t +s" ,":set -w" ,":redir hlintvar1 :show modules" ,":cmd return $ \":! hlint \" ++ unwords (map (takeWhile (/=',') . drop 2 . dropWhile (/= '(')) $ lines hlintvar1) ++ \" \" ++ " ++ show extra ,":set +t +s -Wall" ] :} hlint-2.0.11/data/hlint.10000644000000000000000000000242513210071537013212 0ustar0000000000000000.TH HLINT "1" "July 2009" "HLint (C) Neil Mitchell 2006-2009" "User Commands" .SH NAME HLint \- haskell source code suggestions .SH SYNOPSIS .B hlint [\fIfiles/directories\fR] [\fIoptions\fR] .SH DESCRIPTION \fIHLint\fR is a tool for suggesting possible improvements to Haskell code. These suggestions include ideas such as using alternative functions, simplifying code and spotting redundancies. .SH OPTIONS .TP \fB\-?\fR \fB\-\-help\fR Display help message .TP \fB\-v\fR \fB\-\-version\fR Display version information .TP \fB\-r[file]\fR \fB\-\-report\fR[=\fIfile\fR] Generate a report in HTML .TP \fB\-h\fR \fIfile\fR \fB\-\-hint\fR=\fIfile\fR Hint/ignore file to use .TP \fB\-c\fR \fB\-\-color\fR, \fB\-\-colour\fR Color the output (requires ANSI terminal) .TP \fB\-i\fR \fImessage\fR \fB\-\-ignore\fR=\fImessage\fR Ignore a particular hint .TP \fB\-s\fR \fB\-\-show\fR Show all ignored ideas .TP \fB\-t\fR \fB\-\-test\fR Run in test mode .SH EXAMPLE "To check all Haskell files in 'src' and generate a report type:" .IP hlint src \fB\-\-report\fR .SH "SEE ALSO" The full documentation for .B HLint is available in \fI/usr/share/doc/hlint/hlint.html\fI. .SH AUTHOR This manual page was written by Joachim Breitner for the Debian system (but may be used by others). hlint-2.0.11/data/default.yaml0000644000000000000000000000334413210071537014323 0ustar0000000000000000# HLint configuration file # https://github.com/ndmitchell/hlint ########################## # This file contains a template configuration file, which is typically # placed as .hlint.yaml in the root of your project # Specify additional command line arguments # # - arguments: [--color, --cpp-simple, -XQuasiQuotes] # Control which extensions/flags/modules/functions can be used # # - extensions: # - default: false # all extension are banned by default # - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used # - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module # # - flags: # - {name: -w, within: []} # -w is allowed nowhere # # - modules: # - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set' # - {name: Control.Arrow, within: []} # Certain modules are banned entirely # # - functions: # - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules # Add custom hints for this project # # Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar" # - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x} # Turn on hints that are off by default # # Ban "module X(module X) where", to require a real export list # - warn: {name: Use explicit module export list} # # Replace a $ b $ c with a . b $ c # - group: {name: dollar, enabled: true} # # Generalise map to fmap, ++ to <> # - group: {name: generalise, enabled: true} # Ignore some builtin hints # - ignore: {name: Use let} # - ignore: {name: Use const, within: SpecialModule} # Only within certain modules # Define some custom infix operators # - fixity: infixr 3 ~^#^~ # To generate a suitable file for HLint do: # $ hlint --default > .hlint.yaml